|
|
|
@ -24,8 +24,8 @@ import androidx.compose.foundation.lazy.grid.LazyGridState
|
|
|
|
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemInfo
|
|
|
|
|
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
|
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
|
import androidx.compose.runtime.State
|
|
|
|
|
import androidx.compose.runtime.produceState
|
|
|
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
|
import androidx.compose.runtime.snapshotFlow
|
|
|
|
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
|
|
|
import kotlinx.coroutines.flow.filterNotNull
|
|
|
|
@ -41,57 +41,57 @@ import kotlin.math.min
|
|
|
|
|
fun LazyListState.scrollbarState(
|
|
|
|
|
itemsAvailable: Int,
|
|
|
|
|
itemIndex: (LazyListItemInfo) -> Int = LazyListItemInfo::index,
|
|
|
|
|
): State<ScrollbarState> = produceState(
|
|
|
|
|
initialValue = ScrollbarState.FULL,
|
|
|
|
|
key1 = this,
|
|
|
|
|
key2 = itemsAvailable,
|
|
|
|
|
) {
|
|
|
|
|
snapshotFlow {
|
|
|
|
|
if (itemsAvailable == 0) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val visibleItemsInfo = layoutInfo.visibleItemsInfo
|
|
|
|
|
if (visibleItemsInfo.isEmpty()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val firstIndex = min(
|
|
|
|
|
a = interpolateFirstItemIndex(
|
|
|
|
|
visibleItems = visibleItemsInfo,
|
|
|
|
|
itemSize = { it.size },
|
|
|
|
|
offset = { it.offset },
|
|
|
|
|
nextItemOnMainAxis = { first -> visibleItemsInfo.find { it != first } },
|
|
|
|
|
itemIndex = itemIndex,
|
|
|
|
|
),
|
|
|
|
|
b = itemsAvailable.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
if (firstIndex.isNaN()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo ->
|
|
|
|
|
itemVisibilityPercentage(
|
|
|
|
|
itemSize = itemInfo.size,
|
|
|
|
|
itemStartOffset = itemInfo.offset,
|
|
|
|
|
viewportStartOffset = layoutInfo.viewportStartOffset,
|
|
|
|
|
viewportEndOffset = layoutInfo.viewportEndOffset,
|
|
|
|
|
): ScrollbarState {
|
|
|
|
|
val state = remember { ScrollbarState() }
|
|
|
|
|
LaunchedEffect(this, itemsAvailable) {
|
|
|
|
|
snapshotFlow {
|
|
|
|
|
if (itemsAvailable == 0) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val visibleItemsInfo = layoutInfo.visibleItemsInfo
|
|
|
|
|
if (visibleItemsInfo.isEmpty()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val firstIndex = min(
|
|
|
|
|
a = interpolateFirstItemIndex(
|
|
|
|
|
visibleItems = visibleItemsInfo,
|
|
|
|
|
itemSize = { it.size },
|
|
|
|
|
offset = { it.offset },
|
|
|
|
|
nextItemOnMainAxis = { first -> visibleItemsInfo.find { it != first } },
|
|
|
|
|
itemIndex = itemIndex,
|
|
|
|
|
),
|
|
|
|
|
b = itemsAvailable.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
if (firstIndex.isNaN()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo ->
|
|
|
|
|
itemVisibilityPercentage(
|
|
|
|
|
itemSize = itemInfo.size,
|
|
|
|
|
itemStartOffset = itemInfo.offset,
|
|
|
|
|
viewportStartOffset = layoutInfo.viewportStartOffset,
|
|
|
|
|
viewportEndOffset = layoutInfo.viewportEndOffset,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val thumbTravelPercent = min(
|
|
|
|
|
a = firstIndex / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
val thumbSizePercent = min(
|
|
|
|
|
a = itemsVisible / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
scrollbarStateValue(
|
|
|
|
|
thumbSizePercent = thumbSizePercent,
|
|
|
|
|
thumbMovedPercent = when {
|
|
|
|
|
layoutInfo.reverseLayout -> 1f - thumbTravelPercent
|
|
|
|
|
else -> thumbTravelPercent
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val thumbTravelPercent = min(
|
|
|
|
|
a = firstIndex / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
val thumbSizePercent = min(
|
|
|
|
|
a = itemsVisible / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
ScrollbarState(
|
|
|
|
|
thumbSizePercent = thumbSizePercent,
|
|
|
|
|
thumbMovedPercent = when {
|
|
|
|
|
layoutInfo.reverseLayout -> 1f - thumbTravelPercent
|
|
|
|
|
else -> thumbTravelPercent
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.filterNotNull()
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.collect { state.onScroll(it) }
|
|
|
|
|
}
|
|
|
|
|
.filterNotNull()
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.collect { value = it }
|
|
|
|
|
return state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -104,67 +104,67 @@ fun LazyListState.scrollbarState(
|
|
|
|
|
fun LazyGridState.scrollbarState(
|
|
|
|
|
itemsAvailable: Int,
|
|
|
|
|
itemIndex: (LazyGridItemInfo) -> Int = LazyGridItemInfo::index,
|
|
|
|
|
): State<ScrollbarState> = produceState(
|
|
|
|
|
initialValue = ScrollbarState.FULL,
|
|
|
|
|
key1 = this,
|
|
|
|
|
key2 = itemsAvailable,
|
|
|
|
|
) {
|
|
|
|
|
snapshotFlow {
|
|
|
|
|
if (itemsAvailable == 0) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val visibleItemsInfo = layoutInfo.visibleItemsInfo
|
|
|
|
|
if (visibleItemsInfo.isEmpty()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val firstIndex = min(
|
|
|
|
|
a = interpolateFirstItemIndex(
|
|
|
|
|
visibleItems = visibleItemsInfo,
|
|
|
|
|
itemSize = { layoutInfo.orientation.valueOf(it.size) },
|
|
|
|
|
offset = { layoutInfo.orientation.valueOf(it.offset) },
|
|
|
|
|
nextItemOnMainAxis = { first ->
|
|
|
|
|
when (layoutInfo.orientation) {
|
|
|
|
|
Orientation.Vertical -> visibleItemsInfo.find {
|
|
|
|
|
it != first && it.row != first.row
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Orientation.Horizontal -> visibleItemsInfo.find {
|
|
|
|
|
it != first && it.column != first.column
|
|
|
|
|
): ScrollbarState {
|
|
|
|
|
val state = remember { ScrollbarState() }
|
|
|
|
|
LaunchedEffect(this, itemsAvailable) {
|
|
|
|
|
snapshotFlow {
|
|
|
|
|
if (itemsAvailable == 0) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val visibleItemsInfo = layoutInfo.visibleItemsInfo
|
|
|
|
|
if (visibleItemsInfo.isEmpty()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val firstIndex = min(
|
|
|
|
|
a = interpolateFirstItemIndex(
|
|
|
|
|
visibleItems = visibleItemsInfo,
|
|
|
|
|
itemSize = { layoutInfo.orientation.valueOf(it.size) },
|
|
|
|
|
offset = { layoutInfo.orientation.valueOf(it.offset) },
|
|
|
|
|
nextItemOnMainAxis = { first ->
|
|
|
|
|
when (layoutInfo.orientation) {
|
|
|
|
|
Orientation.Vertical -> visibleItemsInfo.find {
|
|
|
|
|
it != first && it.row != first.row
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Orientation.Horizontal -> visibleItemsInfo.find {
|
|
|
|
|
it != first && it.column != first.column
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
itemIndex = itemIndex,
|
|
|
|
|
),
|
|
|
|
|
b = itemsAvailable.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
if (firstIndex.isNaN()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo ->
|
|
|
|
|
itemVisibilityPercentage(
|
|
|
|
|
itemSize = layoutInfo.orientation.valueOf(itemInfo.size),
|
|
|
|
|
itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset),
|
|
|
|
|
viewportStartOffset = layoutInfo.viewportStartOffset,
|
|
|
|
|
viewportEndOffset = layoutInfo.viewportEndOffset,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val thumbTravelPercent = min(
|
|
|
|
|
a = firstIndex / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
val thumbSizePercent = min(
|
|
|
|
|
a = itemsVisible / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
scrollbarStateValue(
|
|
|
|
|
thumbSizePercent = thumbSizePercent,
|
|
|
|
|
thumbMovedPercent = when {
|
|
|
|
|
layoutInfo.reverseLayout -> 1f - thumbTravelPercent
|
|
|
|
|
else -> thumbTravelPercent
|
|
|
|
|
},
|
|
|
|
|
itemIndex = itemIndex,
|
|
|
|
|
),
|
|
|
|
|
b = itemsAvailable.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
if (firstIndex.isNaN()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo ->
|
|
|
|
|
itemVisibilityPercentage(
|
|
|
|
|
itemSize = layoutInfo.orientation.valueOf(itemInfo.size),
|
|
|
|
|
itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset),
|
|
|
|
|
viewportStartOffset = layoutInfo.viewportStartOffset,
|
|
|
|
|
viewportEndOffset = layoutInfo.viewportEndOffset,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val thumbTravelPercent = min(
|
|
|
|
|
a = firstIndex / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
val thumbSizePercent = min(
|
|
|
|
|
a = itemsVisible / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
ScrollbarState(
|
|
|
|
|
thumbSizePercent = thumbSizePercent,
|
|
|
|
|
thumbMovedPercent = when {
|
|
|
|
|
layoutInfo.reverseLayout -> 1f - thumbTravelPercent
|
|
|
|
|
else -> thumbTravelPercent
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.filterNotNull()
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.collect { state.onScroll(it) }
|
|
|
|
|
}
|
|
|
|
|
.filterNotNull()
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.collect { value = it }
|
|
|
|
|
return state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -178,56 +178,56 @@ fun LazyGridState.scrollbarState(
|
|
|
|
|
fun LazyStaggeredGridState.scrollbarState(
|
|
|
|
|
itemsAvailable: Int,
|
|
|
|
|
itemIndex: (LazyStaggeredGridItemInfo) -> Int = LazyStaggeredGridItemInfo::index,
|
|
|
|
|
): State<ScrollbarState> = produceState(
|
|
|
|
|
initialValue = ScrollbarState.FULL,
|
|
|
|
|
key1 = this,
|
|
|
|
|
key2 = itemsAvailable,
|
|
|
|
|
) {
|
|
|
|
|
snapshotFlow {
|
|
|
|
|
if (itemsAvailable == 0) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val visibleItemsInfo = layoutInfo.visibleItemsInfo
|
|
|
|
|
if (visibleItemsInfo.isEmpty()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val firstIndex = min(
|
|
|
|
|
a = interpolateFirstItemIndex(
|
|
|
|
|
visibleItems = visibleItemsInfo,
|
|
|
|
|
itemSize = { layoutInfo.orientation.valueOf(it.size) },
|
|
|
|
|
offset = { layoutInfo.orientation.valueOf(it.offset) },
|
|
|
|
|
nextItemOnMainAxis = { first ->
|
|
|
|
|
visibleItemsInfo.find { it != first && it.lane == first.lane }
|
|
|
|
|
},
|
|
|
|
|
itemIndex = itemIndex,
|
|
|
|
|
),
|
|
|
|
|
b = itemsAvailable.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
if (firstIndex.isNaN()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo ->
|
|
|
|
|
itemVisibilityPercentage(
|
|
|
|
|
itemSize = layoutInfo.orientation.valueOf(itemInfo.size),
|
|
|
|
|
itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset),
|
|
|
|
|
viewportStartOffset = layoutInfo.viewportStartOffset,
|
|
|
|
|
viewportEndOffset = layoutInfo.viewportEndOffset,
|
|
|
|
|
): ScrollbarState {
|
|
|
|
|
val state = remember { ScrollbarState() }
|
|
|
|
|
LaunchedEffect(this, itemsAvailable) {
|
|
|
|
|
snapshotFlow {
|
|
|
|
|
if (itemsAvailable == 0) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val visibleItemsInfo = layoutInfo.visibleItemsInfo
|
|
|
|
|
if (visibleItemsInfo.isEmpty()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val firstIndex = min(
|
|
|
|
|
a = interpolateFirstItemIndex(
|
|
|
|
|
visibleItems = visibleItemsInfo,
|
|
|
|
|
itemSize = { layoutInfo.orientation.valueOf(it.size) },
|
|
|
|
|
offset = { layoutInfo.orientation.valueOf(it.offset) },
|
|
|
|
|
nextItemOnMainAxis = { first ->
|
|
|
|
|
visibleItemsInfo.find { it != first && it.lane == first.lane }
|
|
|
|
|
},
|
|
|
|
|
itemIndex = itemIndex,
|
|
|
|
|
),
|
|
|
|
|
b = itemsAvailable.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
if (firstIndex.isNaN()) return@snapshotFlow null
|
|
|
|
|
|
|
|
|
|
val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo ->
|
|
|
|
|
itemVisibilityPercentage(
|
|
|
|
|
itemSize = layoutInfo.orientation.valueOf(itemInfo.size),
|
|
|
|
|
itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset),
|
|
|
|
|
viewportStartOffset = layoutInfo.viewportStartOffset,
|
|
|
|
|
viewportEndOffset = layoutInfo.viewportEndOffset,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val thumbTravelPercent = min(
|
|
|
|
|
a = firstIndex / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
val thumbSizePercent = min(
|
|
|
|
|
a = itemsVisible / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
scrollbarStateValue(
|
|
|
|
|
thumbSizePercent = thumbSizePercent,
|
|
|
|
|
thumbMovedPercent = thumbTravelPercent,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val thumbTravelPercent = min(
|
|
|
|
|
a = firstIndex / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
val thumbSizePercent = min(
|
|
|
|
|
a = itemsVisible / itemsAvailable,
|
|
|
|
|
b = 1f,
|
|
|
|
|
)
|
|
|
|
|
ScrollbarState(
|
|
|
|
|
thumbSizePercent = thumbSizePercent,
|
|
|
|
|
thumbMovedPercent = thumbTravelPercent,
|
|
|
|
|
)
|
|
|
|
|
.filterNotNull()
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.collect { state.onScroll(it) }
|
|
|
|
|
}
|
|
|
|
|
.filterNotNull()
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.collect { value = it }
|
|
|
|
|
return state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private inline fun <T> List<T>.floatSumOf(selector: (T) -> Float): Float {
|
|
|
|
|