Skip to content

Commit

Permalink
Added support to staggered grids
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahmad-Hamwi committed Sep 4, 2024
1 parent cda6f4c commit 3d9550b
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.github.ahmad_hamwi.compose.pagination

import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

@Composable
fun <KEY, T> PaginatedLazyHorizontalStaggeredGrid(
paginationState: PaginationState<KEY, T>,
rows: StaggeredGridCells,
modifier: Modifier = Modifier,
firstPageProgressIndicator: @Composable () -> Unit = {},
newPageProgressIndicator: @Composable () -> Unit = {},
firstPageErrorIndicator: @Composable (e: Exception) -> Unit = {},
newPageErrorIndicator: @Composable (e: Exception) -> Unit = {},
state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(0.dp),
horizontalItemSpacing: Dp = 0.dp,
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyStaggeredGridScope.() -> Unit
) {
PaginatedLazyScrollable<KEY, T, LazyStaggeredGridState, LazyStaggeredGridScope>(
paginationState,
modifier,
firstPageProgressIndicator,
newPageProgressIndicator,
firstPageErrorIndicator,
newPageErrorIndicator,
state,
) { paginatedItemsHandler ->
LazyHorizontalStaggeredGrid(
rows,
modifier,
state,
contentPadding,
reverseLayout,
verticalArrangement,
horizontalItemSpacing,
flingBehavior,
userScrollEnabled,
) {
paginatedItemsHandler {
content()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ 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.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -62,6 +64,12 @@ internal fun <KEY, T, LAZY_STATE, LAZY_SCROLLABLE_SCOPE> PaginatedLazyScrollable
.map { item -> item?.index ?: Int.MIN_VALUE }
}

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

is LazyListState -> {
lastVisibleItemIndex =
snapshotFlow { state.layoutInfo.visibleItemsInfo.lastOrNull() }
Expand Down Expand Up @@ -107,6 +115,7 @@ internal fun <KEY, T, LAZY_STATE, LAZY_SCROLLABLE_SCOPE> PaginatedLazyScrollable
when (this) {
is LazyListScope -> item(key) { content() }
is LazyGridScope -> item(key) { content() }
is LazyStaggeredGridScope -> item(key) { content() }
else -> throw IllegalStateException("Unsupported Lazy scrollable scope type")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.github.ahmad_hamwi.compose.pagination

import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

@Composable
fun <KEY, T> PaginatedLazyVerticalStaggeredGrid(
paginationState: PaginationState<KEY, T>,
columns: StaggeredGridCells,
modifier: Modifier = Modifier,
firstPageProgressIndicator: @Composable () -> Unit = {},
newPageProgressIndicator: @Composable () -> Unit = {},
firstPageErrorIndicator: @Composable (e: Exception) -> Unit = {},
newPageErrorIndicator: @Composable (e: Exception) -> Unit = {},
state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalItemSpacing: Dp = 0.dp,
horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(0.dp),
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyStaggeredGridScope.() -> Unit
) {
PaginatedLazyScrollable<KEY, T, LazyStaggeredGridState, LazyStaggeredGridScope>(
paginationState,
modifier,
firstPageProgressIndicator,
newPageProgressIndicator,
firstPageErrorIndicator,
newPageErrorIndicator,
state,
) { paginatedItemsHandler ->
LazyVerticalStaggeredGrid(
columns,
modifier,
state,
contentPadding,
reverseLayout,
verticalItemSpacing,
horizontalArrangement,
flingBehavior,
userScrollEnabled,
) {
paginatedItemsHandler {
content()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.github.ahmad_hamwi.compose.pagination

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import kotlin.test.Test

class PaginatedLazyHorizontalStaggeredGridTest : PaginatedLazyScrollableTest() {
@Suppress("TestFunctionName")
@Composable
override fun SutComposable(
paginationState: PaginationState<Int, String>
) {
PaginatedLazyHorizontalStaggeredGrid(
modifier = Modifier.testTag(LAZY_SCROLLABLE_TAG),
paginationState = paginationState,
firstPageProgressIndicator = {
Box(modifier = Modifier.testTag(FIRST_PAGE_PROGRESS_INDICATOR_TAG))
},
firstPageErrorIndicator = {
Box(modifier = Modifier.testTag(FIRST_PAGE_ERROR_INDICATOR_TAG)) {
Text(it.message.toString())
}
},
newPageProgressIndicator = {
Box(modifier = Modifier.testTag(NEW_PAGE_PROGRESS_INDICATOR_TAG))
},
newPageErrorIndicator = {
Box(modifier = Modifier.testTag(NEW_PAGE_ERROR_INDICATOR_TAG)) {
Text(it.message.toString())
}
},
rows = StaggeredGridCells.Fixed(3),
) {
itemsIndexed(
items = paginationState.allItems!!,
key = { i, _ -> i }
) { _, _ ->
Box(
modifier = Modifier
.fillMaxWidth()
.size(100.dp)
.testTag(ITEM_CONTENT_TAG)
) {}
}
}
}

@Test
override fun firstPageProgressIndicatorShownWhenNullPage() =
super.firstPageProgressIndicatorShownWhenNullPage()

@Test
override fun firstPageProgressIndicatorHiddenWhenAPageHasBeenAppended() =
super.firstPageProgressIndicatorHiddenWhenAPageHasBeenAppended()

@Test
override fun firstPageErrorIsShownWhenNoPageAndErrorHappened() =
super.firstPageErrorIsShownWhenNoPageAndErrorHappened()

@Test
override fun firstPageIsShownWhenPutPageIsTriggeredForTheFirstTime() =
super.firstPageIsShownWhenPutPageIsTriggeredForTheFirstTime()

@Test
override fun scrollingDownTheListWillShowProgressAndTriggerPageRequest() =
super.scrollingDownTheListWillShowProgressAndTriggerPageRequest()

@Test
override fun scrollingDownTheListWillShowErrorAndTriggerPageRequest() =
super.scrollingDownTheListWillShowErrorAndTriggerPageRequest()

@Test
override fun appendingLastPagePreventsLoadingAndNewPageRequests() =
super.appendingLastPagePreventsLoadingAndNewPageRequests()

@Test
override fun retryFirstFailedRequestWouldRequestAgainTheSamePageAndShowProgress() =
super.retryFirstFailedRequestWouldRequestAgainTheSamePageAndShowProgress()

@Test
override fun retryNewPageFailedRequestWouldRequestAgainTheSamePageAndShowProgress() =
super.retryNewPageFailedRequestWouldRequestAgainTheSamePageAndShowProgress()

@Test
override fun refreshingResetsTheStateAndAnInitialLoadStarts() =
super.refreshingResetsTheStateAndAnInitialLoadStarts()

@Test
override fun firstPageErrorThenRefreshThenFirstPageRequestedAgainThenShowFirstPage() =
super.firstPageErrorThenRefreshThenFirstPageRequestedAgainThenShowFirstPage()

@Test
override fun firstPageLoadedThenRefreshThenFirstPageRequestedAgainThenShowFirstPage() =
super.firstPageLoadedThenRefreshThenFirstPageRequestedAgainThenShowFirstPage()

@Test
override fun loadsOnlyOnePageAfterAScroll() =
super.loadsOnlyOnePageAfterAScroll()

@Test
override fun initialPageIs2WouldLoadPage2() =
super.initialPageIs2WouldLoadPage2()

@Test
override fun refreshingWithInitialPageOf2WouldLoadPage2() =
super.refreshingWithInitialPageOf2WouldLoadPage2()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.github.ahmad_hamwi.compose.pagination

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.itemsIndexed
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import kotlin.test.Test

class PaginatedLazyVerticalStaggeredGridTest : PaginatedLazyScrollableTest() {
@Suppress("TestFunctionName")
@Composable
override fun SutComposable(
paginationState: PaginationState<Int, String>
) {
PaginatedLazyVerticalStaggeredGrid(
modifier = Modifier.testTag(LAZY_SCROLLABLE_TAG),
paginationState = paginationState,
firstPageProgressIndicator = {
Box(modifier = Modifier.testTag(FIRST_PAGE_PROGRESS_INDICATOR_TAG))
},
firstPageErrorIndicator = {
Box(modifier = Modifier.testTag(FIRST_PAGE_ERROR_INDICATOR_TAG)) {
Text(it.message.toString())
}
},
newPageProgressIndicator = {
Box(modifier = Modifier.testTag(NEW_PAGE_PROGRESS_INDICATOR_TAG))
},
newPageErrorIndicator = {
Box(modifier = Modifier.testTag(NEW_PAGE_ERROR_INDICATOR_TAG)) {
Text(it.message.toString())
}
},
columns = StaggeredGridCells.Fixed(3),
) {
itemsIndexed(
items = paginationState.allItems!!,
key = { i, _ -> i }
) { _, _ ->
Box(
modifier = Modifier
.fillMaxWidth()
.size(100.dp)
.testTag(ITEM_CONTENT_TAG)
) {}
}
}
}

@Test
override fun firstPageProgressIndicatorShownWhenNullPage() =
super.firstPageProgressIndicatorShownWhenNullPage()

@Test
override fun firstPageProgressIndicatorHiddenWhenAPageHasBeenAppended() =
super.firstPageProgressIndicatorHiddenWhenAPageHasBeenAppended()

@Test
override fun firstPageErrorIsShownWhenNoPageAndErrorHappened() =
super.firstPageErrorIsShownWhenNoPageAndErrorHappened()

@Test
override fun firstPageIsShownWhenPutPageIsTriggeredForTheFirstTime() =
super.firstPageIsShownWhenPutPageIsTriggeredForTheFirstTime()

@Test
override fun scrollingDownTheListWillShowProgressAndTriggerPageRequest() =
super.scrollingDownTheListWillShowProgressAndTriggerPageRequest()

@Test
override fun scrollingDownTheListWillShowErrorAndTriggerPageRequest() =
super.scrollingDownTheListWillShowErrorAndTriggerPageRequest()

@Test
override fun appendingLastPagePreventsLoadingAndNewPageRequests() =
super.appendingLastPagePreventsLoadingAndNewPageRequests()

@Test
override fun retryFirstFailedRequestWouldRequestAgainTheSamePageAndShowProgress() =
super.retryFirstFailedRequestWouldRequestAgainTheSamePageAndShowProgress()

@Test
override fun retryNewPageFailedRequestWouldRequestAgainTheSamePageAndShowProgress() =
super.retryNewPageFailedRequestWouldRequestAgainTheSamePageAndShowProgress()

@Test
override fun refreshingResetsTheStateAndAnInitialLoadStarts() =
super.refreshingResetsTheStateAndAnInitialLoadStarts()

@Test
override fun firstPageErrorThenRefreshThenFirstPageRequestedAgainThenShowFirstPage() =
super.firstPageErrorThenRefreshThenFirstPageRequestedAgainThenShowFirstPage()

@Test
override fun firstPageLoadedThenRefreshThenFirstPageRequestedAgainThenShowFirstPage() =
super.firstPageLoadedThenRefreshThenFirstPageRequestedAgainThenShowFirstPage()

@Test
override fun loadsOnlyOnePageAfterAScroll() =
super.loadsOnlyOnePageAfterAScroll()

@Test
override fun initialPageIs2WouldLoadPage2() =
super.initialPageIs2WouldLoadPage2()

@Test
override fun refreshingWithInitialPageOf2WouldLoadPage2() =
super.refreshingWithInitialPageOf2WouldLoadPage2()
}

0 comments on commit 3d9550b

Please sign in to comment.