Refactoring for div-pager

commit_hash:be2c5b6d9f4c39289d26bfe83f761df66429a7d9
This commit is contained in:
burstein
2026-04-15 17:39:48 +03:00
parent 4ecad657e3
commit 9570462504
90 changed files with 362 additions and 330 deletions
+5 -1
View File
@@ -651,6 +651,11 @@
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/gradient/DivGradient.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/gradient/DivGradient.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/gradient/LinearGradientBrush.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/gradient/LinearGradientBrush.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/gradient/RadialGradientBrush.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/gradient/RadialGradientBrush.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/AdjustScrollToItem.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/AdjustScrollToItem.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/IntrinsicSizeBarrier.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/IntrinsicSizeBarrier.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/OrientedLazyList.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/OrientedLazyList.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/ScrollAlignment.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/ScrollAlignment.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/ScrollableChildItem.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/utils/scroll/ScrollableChildItem.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/variables/DivVariableAdapter.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/variables/DivVariableAdapter.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivBlockView.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivSeparatorView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/DivSeparatorView.kt",
@@ -666,7 +671,6 @@
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/container/wrap/WrapSeparatorDrawing.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/container/wrap/WrapSeparatorDrawing.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/DivGalleryView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/DivGalleryView.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryAlignment.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryAlignment.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryChildItem.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryChildItem.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryGridView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryGridView.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryListView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/gallery/GalleryListView.kt",
"client/android/compose/src/main/kotlin/com/yandex/div/compose/views/image/DivImageView.kt":"divkit/public/client/android/compose/src/main/kotlin/com/yandex/div/compose/views/image/DivImageView.kt",
@@ -21,27 +21,28 @@ internal fun DivAlignmentVertical.toVerticalAlignment(): Alignment.Vertical =
internal fun toAlignment(
horizontal: DivAlignmentHorizontal,
vertical: DivAlignmentVertical
): Alignment {
return when (horizontal) {
DivAlignmentHorizontal.LEFT,
DivAlignmentHorizontal.START -> when (vertical) {
DivAlignmentVertical.TOP -> Alignment.TopStart
DivAlignmentVertical.CENTER,
DivAlignmentVertical.BASELINE -> Alignment.CenterStart
DivAlignmentVertical.BOTTOM -> Alignment.BottomStart
}
DivAlignmentHorizontal.CENTER -> when (vertical) {
DivAlignmentVertical.TOP -> Alignment.TopCenter
DivAlignmentVertical.CENTER,
DivAlignmentVertical.BASELINE -> Alignment.Center
DivAlignmentVertical.BOTTOM -> Alignment.BottomCenter
}
DivAlignmentHorizontal.RIGHT,
DivAlignmentHorizontal.END -> when (vertical) {
DivAlignmentVertical.TOP -> Alignment.TopEnd
DivAlignmentVertical.CENTER,
DivAlignmentVertical.BASELINE -> Alignment.CenterEnd
DivAlignmentVertical.BOTTOM -> Alignment.BottomEnd
}
): Alignment = combineAlignment(
horizontal.toHorizontalAlignment(),
vertical.toVerticalAlignment(),
)
internal fun combineAlignment(
horizontal: Alignment.Horizontal,
vertical: Alignment.Vertical,
): Alignment = when (horizontal) {
Alignment.Start -> when (vertical) {
Alignment.Top -> Alignment.TopStart
Alignment.CenterVertically -> Alignment.CenterStart
else -> Alignment.BottomStart
}
Alignment.CenterHorizontally -> when (vertical) {
Alignment.Top -> Alignment.TopCenter
Alignment.CenterVertically -> Alignment.Center
else -> Alignment.BottomCenter
}
else -> when (vertical) {
Alignment.Top -> Alignment.TopEnd
Alignment.CenterVertically -> Alignment.CenterEnd
else -> Alignment.BottomEnd
}
}
@@ -0,0 +1,30 @@
package com.yandex.div.compose.utils.scroll
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.flow.first
@Composable
internal fun AdjustScrollToItem(
listState: LazyListState,
targetIndex: Int,
desiredOffset: (viewportSize: Int, itemSize: Int) -> Int,
) {
LaunchedEffect(Unit) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo }
.first { items -> items.any { it.index == targetIndex } }
val layoutInfo = listState.layoutInfo
val targetItem = layoutInfo.visibleItemsInfo.firstOrNull { it.index == targetIndex }
?: return@LaunchedEffect
val viewportSize = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset
val offset = desiredOffset(viewportSize, targetItem.size)
listState.scroll {
scrollBy((targetItem.offset - offset).toFloat())
}
}
}
@@ -0,0 +1,28 @@
package com.yandex.div.compose.utils.scroll
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.IntrinsicMeasureScope
import androidx.compose.ui.layout.LayoutModifier
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.unit.Constraints
/**
* Blocks intrinsic measurement queries from reaching SubcomposeLayout-based children
* (LazyRow, LazyColumn, etc.) by returning 0 for all intrinsic measurements.
* Outer modifiers (like requiredWidth) coerce the result to the correct value.
*/
internal val IntrinsicSizeBarrier = Modifier.then(object : LayoutModifier {
override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints): MeasureResult {
val placeable = measurable.measure(constraints)
return layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
override fun IntrinsicMeasureScope.maxIntrinsicWidth(measurable: IntrinsicMeasurable, height: Int) = 0
override fun IntrinsicMeasureScope.minIntrinsicWidth(measurable: IntrinsicMeasurable, height: Int) = 0
override fun IntrinsicMeasureScope.maxIntrinsicHeight(measurable: IntrinsicMeasurable, width: Int) = 0
override fun IntrinsicMeasureScope.minIntrinsicHeight(measurable: IntrinsicMeasurable, width: Int) = 0
})
@@ -0,0 +1,46 @@
package com.yandex.div.compose.utils.scroll
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
@Composable
internal fun OrientedLazyList(
isHorizontal: Boolean,
modifier: Modifier,
listState: LazyListState,
contentPadding: PaddingValues,
itemSpacing: Dp,
crossAxisAlignment: CrossAxisAlignment,
flingBehavior: FlingBehavior,
content: LazyListScope.() -> Unit,
) {
if (isHorizontal) {
LazyRow(
modifier = modifier,
state = listState,
contentPadding = contentPadding,
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
verticalAlignment = crossAxisAlignment.toVerticalAlignment(),
flingBehavior = flingBehavior,
content = content,
)
} else {
LazyColumn(
modifier = modifier,
state = listState,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(itemSpacing),
horizontalAlignment = crossAxisAlignment.toHorizontalAlignment(),
flingBehavior = flingBehavior,
content = content,
)
}
}
@@ -0,0 +1,69 @@
package com.yandex.div.compose.utils.scroll
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import com.yandex.div2.DivAlignmentHorizontal
import com.yandex.div2.DivAlignmentVertical
internal enum class CrossAxisAlignment {
START, CENTER, END;
fun toVerticalAlignment(): Alignment.Vertical = when (this) {
START -> Alignment.Top
CENTER -> Alignment.CenterVertically
END -> Alignment.Bottom
}
fun toHorizontalAlignment(): Alignment.Horizontal = when (this) {
START -> Alignment.Start
CENTER -> Alignment.CenterHorizontally
END -> Alignment.End
}
fun toBoxAlignment(isHorizontal: Boolean): Alignment =
if (isHorizontal) {
when (this) {
START -> Alignment.TopStart
CENTER -> Alignment.CenterStart
END -> Alignment.BottomStart
}
} else {
when (this) {
START -> Alignment.TopStart
CENTER -> Alignment.TopCenter
END -> Alignment.TopEnd
}
}
}
internal fun DivAlignmentHorizontal.toCrossAxisAlignment(): CrossAxisAlignment =
when (this) {
DivAlignmentHorizontal.LEFT,
DivAlignmentHorizontal.START -> CrossAxisAlignment.START
DivAlignmentHorizontal.CENTER -> CrossAxisAlignment.CENTER
DivAlignmentHorizontal.RIGHT,
DivAlignmentHorizontal.END -> CrossAxisAlignment.END
}
internal fun DivAlignmentVertical.toCrossAxisAlignment(): CrossAxisAlignment =
when (this) {
DivAlignmentVertical.TOP,
DivAlignmentVertical.BASELINE -> CrossAxisAlignment.START
DivAlignmentVertical.CENTER -> CrossAxisAlignment.CENTER
DivAlignmentVertical.BOTTOM -> CrossAxisAlignment.END
}
internal fun PaddingValues.getScrollAxisPaddings(
isHorizontal: Boolean,
layoutDirection: LayoutDirection,
): Pair<Dp, Dp> {
return if (isHorizontal) {
Pair(calculateStartPadding(layoutDirection), calculateEndPadding(layoutDirection))
} else {
Pair(calculateTopPadding(), calculateBottomPadding())
}
}
@@ -0,0 +1,31 @@
package com.yandex.div.compose.utils.scroll
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.yandex.div.compose.utils.observedValue
import com.yandex.div.compose.views.DivBlockView
import com.yandex.div2.Div
@Composable
internal fun ScrollableChildItem(
childDiv: Div,
modifier: Modifier,
isHorizontal: Boolean,
crossAxisAlignment: CrossAxisAlignment
) {
val childBase = childDiv.value()
val childCrossAlignment = if (isHorizontal) {
childBase.alignmentVertical?.observedValue()?.toCrossAxisAlignment()
} else {
childBase.alignmentHorizontal?.observedValue()?.toCrossAxisAlignment()
} ?: crossAxisAlignment
Box(
modifier = modifier,
contentAlignment = childCrossAlignment.toBoxAlignment(isHorizontal),
) {
DivBlockView(childDiv)
}
}
@@ -4,11 +4,11 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.yandex.div.compose.views.DivBlockView
import com.yandex.div.compose.utils.combineAlignment
import com.yandex.div.compose.utils.observedValue
import com.yandex.div.compose.utils.toAlignment
import com.yandex.div.compose.utils.toHorizontalAlignment
import com.yandex.div.compose.utils.toVerticalAlignment
import com.yandex.div.compose.views.DivBlockView
import com.yandex.div2.DivAlignmentHorizontal
import com.yandex.div2.DivAlignmentVertical
import com.yandex.div2.DivContainer
@@ -25,9 +25,9 @@ internal fun ContainerOverlapView(modifier: Modifier, data: DivContainer) {
horizontalAlignment,
verticalAlignment,
)
val defaultAlignment = toAlignment(
horizontalAlignment.toDefaultDivAlignmentHorizontal(),
verticalAlignment.toDefaultDivAlignmentVertical(),
val defaultAlignment = combineAlignment(
horizontalAlignment.toCrossAxisHorizontalAlignment(),
verticalAlignment.toCrossAxisVerticalAlignment(),
)
Box(modifier, contentAlignment = defaultAlignment) {
@@ -53,41 +53,5 @@ private fun resolveOverlapChildAlignment(
?: defaultHorizontal.toCrossAxisHorizontalAlignment()
val vertical = childVertical?.toVerticalAlignment()
?: defaultVertical.toCrossAxisVerticalAlignment()
return BiasAlignment(horizontal, vertical)
return combineAlignment(horizontal, vertical)
}
private fun BiasAlignment(
horizontal: Alignment.Horizontal,
vertical: Alignment.Vertical,
): Alignment = when (horizontal) {
Alignment.Start -> when (vertical) {
Alignment.Top -> Alignment.TopStart
Alignment.CenterVertically -> Alignment.CenterStart
else -> Alignment.BottomStart
}
Alignment.CenterHorizontally -> when (vertical) {
Alignment.Top -> Alignment.TopCenter
Alignment.CenterVertically -> Alignment.Center
else -> Alignment.BottomCenter
}
else -> when (vertical) {
Alignment.Top -> Alignment.TopEnd
Alignment.CenterVertically -> Alignment.CenterEnd
else -> Alignment.BottomEnd
}
}
private fun DivContentAlignmentHorizontal.toDefaultDivAlignmentHorizontal(): DivAlignmentHorizontal =
when (this) {
DivContentAlignmentHorizontal.CENTER -> DivAlignmentHorizontal.CENTER
DivContentAlignmentHorizontal.RIGHT -> DivAlignmentHorizontal.RIGHT
DivContentAlignmentHorizontal.END -> DivAlignmentHorizontal.END
else -> DivAlignmentHorizontal.START
}
private fun DivContentAlignmentVertical.toDefaultDivAlignmentVertical(): DivAlignmentVertical =
when (this) {
DivContentAlignmentVertical.CENTER -> DivAlignmentVertical.CENTER
DivContentAlignmentVertical.BOTTOM -> DivAlignmentVertical.BOTTOM
else -> DivAlignmentVertical.TOP
}
@@ -18,7 +18,7 @@ internal fun DivGalleryView(
data: DivGallery,
) {
val orientation = data.orientation.observedValue()
val itemSpacingDp = data.itemSpacing.observedValue().toDp()
val itemSpacing = data.itemSpacing.observedValue().toDp()
val crossContentAlignment = data.crossContentAlignment.observedValue()
val columnCount = data.columnCount?.observedIntValue() ?: 1
val defaultItem = data.defaultItem.observedIntValue()
@@ -34,8 +34,8 @@ internal fun DivGalleryView(
items = items,
orientation = orientation,
columnCount = columnCount,
itemSpacingDp = itemSpacingDp,
crossSpacingDp = data.crossSpacing?.observedValue()?.toDp() ?: itemSpacingDp,
itemSpacing = itemSpacing,
crossSpacing = data.crossSpacing?.observedValue()?.toDp() ?: itemSpacing,
crossContentAlignment = crossContentAlignment,
contentPadding = contentPadding,
defaultItem = defaultItem,
@@ -48,7 +48,7 @@ internal fun DivGalleryView(
modifier = if (isScrollable) modifier.constrainScrollAxis(isHorizontal) else modifier,
items = items,
orientation = orientation,
itemSpacingDp = itemSpacingDp,
itemSpacing = itemSpacing,
crossContentAlignment = crossContentAlignment,
contentPadding = contentPadding,
defaultItem = defaultItem,
@@ -1,52 +1,18 @@
package com.yandex.div.compose.views.gallery
import androidx.compose.ui.Alignment
import com.yandex.div2.DivAlignmentHorizontal
import com.yandex.div2.DivAlignmentVertical
import com.yandex.div.compose.utils.scroll.CrossAxisAlignment
import com.yandex.div2.DivGallery
internal fun DivGallery.CrossContentAlignment.toVerticalAlignment(): Alignment.Vertical =
when (this) {
DivGallery.CrossContentAlignment.START -> Alignment.Top
DivGallery.CrossContentAlignment.CENTER -> Alignment.CenterVertically
DivGallery.CrossContentAlignment.END -> Alignment.Bottom
}
toCrossAxisAlignment().toVerticalAlignment()
internal fun DivGallery.CrossContentAlignment.toHorizontalAlignment(): Alignment.Horizontal =
when (this) {
DivGallery.CrossContentAlignment.START -> Alignment.Start
DivGallery.CrossContentAlignment.CENTER -> Alignment.CenterHorizontally
DivGallery.CrossContentAlignment.END -> Alignment.End
}
toCrossAxisAlignment().toHorizontalAlignment()
internal fun DivGallery.CrossContentAlignment.toBoxAlignment(isHorizontal: Boolean): Alignment =
if (isHorizontal) {
when (this) {
DivGallery.CrossContentAlignment.START -> Alignment.TopCenter
DivGallery.CrossContentAlignment.CENTER -> Alignment.Center
DivGallery.CrossContentAlignment.END -> Alignment.BottomCenter
}
} else {
when (this) {
DivGallery.CrossContentAlignment.START -> Alignment.CenterStart
DivGallery.CrossContentAlignment.CENTER -> Alignment.Center
DivGallery.CrossContentAlignment.END -> Alignment.CenterEnd
}
}
internal fun DivAlignmentVertical.toGalleryCrossAlignment(): DivGallery.CrossContentAlignment =
internal fun DivGallery.CrossContentAlignment.toCrossAxisAlignment(): CrossAxisAlignment =
when (this) {
DivAlignmentVertical.TOP,
DivAlignmentVertical.BASELINE -> DivGallery.CrossContentAlignment.START
DivAlignmentVertical.CENTER -> DivGallery.CrossContentAlignment.CENTER
DivAlignmentVertical.BOTTOM -> DivGallery.CrossContentAlignment.END
}
internal fun DivAlignmentHorizontal.toGalleryCrossAlignment(): DivGallery.CrossContentAlignment =
when (this) {
DivAlignmentHorizontal.LEFT,
DivAlignmentHorizontal.START -> DivGallery.CrossContentAlignment.START
DivAlignmentHorizontal.CENTER -> DivGallery.CrossContentAlignment.CENTER
DivAlignmentHorizontal.RIGHT,
DivAlignmentHorizontal.END -> DivGallery.CrossContentAlignment.END
DivGallery.CrossContentAlignment.START -> CrossAxisAlignment.START
DivGallery.CrossContentAlignment.CENTER -> CrossAxisAlignment.CENTER
DivGallery.CrossContentAlignment.END -> CrossAxisAlignment.END
}
@@ -1,39 +0,0 @@
package com.yandex.div.compose.views.gallery
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.yandex.div.compose.utils.observedValue
import com.yandex.div.compose.views.DivBlockView
import com.yandex.div2.Div
import com.yandex.div2.DivGallery
@Composable
internal fun GalleryChildItem(
childDiv: Div,
isHorizontal: Boolean,
crossContentAlignment: DivGallery.CrossContentAlignment,
) {
val childBase = childDiv.value()
val childCrossAlignment = if (isHorizontal) {
childBase.alignmentVertical?.observedValue()?.toGalleryCrossAlignment()
} else {
childBase.alignmentHorizontal?.observedValue()?.toGalleryCrossAlignment()
} ?: crossContentAlignment
val modifier = if (isHorizontal) {
Modifier.fillMaxHeight()
} else {
Modifier.fillMaxWidth()
}
Box(
modifier = modifier,
contentAlignment = childCrossAlignment.toBoxAlignment(isHorizontal),
) {
DivBlockView(childDiv)
}
}
@@ -2,6 +2,8 @@ package com.yandex.div.compose.views.gallery
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
@@ -10,6 +12,7 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.yandex.div.compose.utils.scroll.ScrollableChildItem
import com.yandex.div2.Div
import com.yandex.div2.DivGallery
@@ -19,8 +22,8 @@ internal fun GalleryGridView(
items: List<Div>,
orientation: DivGallery.Orientation,
columnCount: Int,
itemSpacingDp: Dp,
crossSpacingDp: Dp,
itemSpacing: Dp,
crossSpacing: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
contentPadding: PaddingValues,
defaultItem: Int,
@@ -35,8 +38,8 @@ internal fun GalleryGridView(
items = items,
gridState = gridState,
columnCount = columnCount,
itemSpacingDp = itemSpacingDp,
crossSpacingDp = crossSpacingDp,
itemSpacing = itemSpacing,
crossSpacing = crossSpacing,
crossContentAlignment = crossContentAlignment,
contentPadding = contentPadding,
)
@@ -45,8 +48,8 @@ internal fun GalleryGridView(
items = items,
gridState = gridState,
columnCount = columnCount,
itemSpacingDp = itemSpacingDp,
crossSpacingDp = crossSpacingDp,
itemSpacing = itemSpacing,
crossSpacing = crossSpacing,
crossContentAlignment = crossContentAlignment,
contentPadding = contentPadding,
)
@@ -59,8 +62,8 @@ private fun GalleryLazyHorizontalGrid(
items: List<Div>,
gridState: LazyGridState,
columnCount: Int,
itemSpacingDp: Dp,
crossSpacingDp: Dp,
itemSpacing: Dp,
crossSpacing: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
contentPadding: PaddingValues,
) {
@@ -69,14 +72,15 @@ private fun GalleryLazyHorizontalGrid(
modifier = modifier,
state = gridState,
contentPadding = contentPadding,
horizontalArrangement = Arrangement.spacedBy(itemSpacingDp),
verticalArrangement = Arrangement.spacedBy(crossSpacingDp),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
verticalArrangement = Arrangement.spacedBy(crossSpacing),
) {
items(count = items.size) { index ->
GalleryChildItem(
ScrollableChildItem(
childDiv = items[index],
modifier = Modifier.fillMaxHeight(),
isHorizontal = true,
crossContentAlignment = crossContentAlignment,
crossAxisAlignment = crossContentAlignment.toCrossAxisAlignment(),
)
}
}
@@ -88,8 +92,8 @@ private fun GalleryLazyVerticalGrid(
items: List<Div>,
gridState: LazyGridState,
columnCount: Int,
itemSpacingDp: Dp,
crossSpacingDp: Dp,
itemSpacing: Dp,
crossSpacing: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
contentPadding: PaddingValues,
) {
@@ -98,14 +102,15 @@ private fun GalleryLazyVerticalGrid(
modifier = modifier,
state = gridState,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
horizontalArrangement = Arrangement.spacedBy(crossSpacingDp),
verticalArrangement = Arrangement.spacedBy(itemSpacing),
horizontalArrangement = Arrangement.spacedBy(crossSpacing),
) {
items(count = items.size) { index ->
GalleryChildItem(
ScrollableChildItem(
childDiv = items[index],
modifier = Modifier.fillMaxWidth(),
isHorizontal = false,
crossContentAlignment = crossContentAlignment,
crossAxisAlignment = crossContentAlignment.toCrossAxisAlignment(),
)
}
}
@@ -1,6 +1,5 @@
package com.yandex.div.compose.views.gallery
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.snapping.SnapPosition
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
@@ -8,26 +7,25 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.yandex.div.compose.utils.scroll.AdjustScrollToItem
import com.yandex.div.compose.utils.scroll.OrientedLazyList
import com.yandex.div.compose.utils.scroll.ScrollableChildItem
import com.yandex.div2.Div
import com.yandex.div2.DivGallery
import kotlinx.coroutines.flow.first
@Composable
internal fun GalleryListView(
modifier: Modifier,
items: List<Div>,
orientation: DivGallery.Orientation,
itemSpacingDp: Dp,
itemSpacing: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
contentPadding: PaddingValues,
defaultItem: Int,
@@ -49,162 +47,84 @@ internal fun GalleryListView(
}
if (isPaging && clampedDefaultItem > 0) {
CenterDefaultItem(listState, clampedDefaultItem)
AdjustScrollToItem(
listState = listState,
targetIndex = clampedDefaultItem,
desiredOffset = { viewportSize, itemSize -> (viewportSize - itemSize) / 2 },
)
}
LazyGalleryListView(
modifier,
items,
orientation,
listState,
contentPadding,
itemSpacingDp,
crossContentAlignment,
flingBehavior
)
val isHorizontal = orientation == DivGallery.Orientation.HORIZONTAL
val crossAlignment = crossContentAlignment.toCrossAxisAlignment()
OrientedLazyList(
isHorizontal = isHorizontal,
modifier = modifier,
listState = listState,
contentPadding = contentPadding,
itemSpacing = itemSpacing,
crossAxisAlignment = crossAlignment,
flingBehavior = flingBehavior,
) {
items(count = items.size) { index ->
ScrollableChildItem(
childDiv = items[index],
modifier = if (isHorizontal) Modifier.fillMaxHeight() else Modifier.fillMaxWidth(),
isHorizontal = isHorizontal,
crossAxisAlignment = crossAlignment,
)
}
}
} else {
NonScrollableGalleryView(
modifier,
items,
orientation,
itemSpacingDp,
itemSpacing,
crossContentAlignment,
contentPadding
)
}
}
@Composable
private fun LazyGalleryListView(
modifier: Modifier,
items: List<Div>,
orientation: DivGallery.Orientation,
listState: LazyListState,
contentPadding: PaddingValues,
itemSpacingDp: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
flingBehavior: FlingBehavior,
) {
when (orientation) {
DivGallery.Orientation.HORIZONTAL -> GalleryLazyRow(
modifier = modifier,
items = items,
listState = listState,
contentPadding = contentPadding,
itemSpacingDp = itemSpacingDp,
crossContentAlignment = crossContentAlignment,
flingBehavior = flingBehavior,
)
DivGallery.Orientation.VERTICAL -> GalleryLazyColumn(
modifier = modifier,
items = items,
listState = listState,
contentPadding = contentPadding,
itemSpacingDp = itemSpacingDp,
crossContentAlignment = crossContentAlignment,
flingBehavior = flingBehavior,
)
}
}
@Composable
private fun CenterDefaultItem(listState: LazyListState, defaultItem: Int) {
LaunchedEffect(Unit) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo }
.first { it.isNotEmpty() }
val layoutInfo = listState.layoutInfo
val viewportSize = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset
val targetItem = layoutInfo.visibleItemsInfo.firstOrNull { it.index == defaultItem }
if (targetItem != null) {
val desiredOffset = (viewportSize - targetItem.size) / 2
listState.scroll {
scrollBy((targetItem.offset - desiredOffset).toFloat())
}
}
}
}
@Composable
private fun GalleryLazyColumn(
modifier: Modifier,
items: List<Div>,
listState: LazyListState,
contentPadding: PaddingValues,
itemSpacingDp: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
flingBehavior: FlingBehavior
) {
LazyColumn(
modifier = modifier,
state = listState,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
horizontalAlignment = crossContentAlignment.toHorizontalAlignment(),
flingBehavior = flingBehavior,
) {
items(count = items.size) { index ->
GalleryChildItem(
childDiv = items[index],
isHorizontal = false,
crossContentAlignment = crossContentAlignment,
)
}
}
}
@Composable
private fun GalleryLazyRow(
modifier: Modifier,
items: List<Div>,
listState: LazyListState,
contentPadding: PaddingValues,
itemSpacingDp: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
flingBehavior: FlingBehavior
) {
LazyRow(
modifier = modifier,
state = listState,
contentPadding = contentPadding,
horizontalArrangement = Arrangement.spacedBy(itemSpacingDp),
verticalAlignment = crossContentAlignment.toVerticalAlignment(),
flingBehavior = flingBehavior,
) {
items(count = items.size) { index ->
GalleryChildItem(
childDiv = items[index],
isHorizontal = true,
crossContentAlignment = crossContentAlignment,
)
}
}
}
@Composable
private fun NonScrollableGalleryView(
modifier: Modifier,
items: List<Div>,
orientation: DivGallery.Orientation,
itemSpacingDp: Dp,
itemSpacing: Dp,
crossContentAlignment: DivGallery.CrossContentAlignment,
contentPadding: PaddingValues,
) {
when (orientation) {
DivGallery.Orientation.HORIZONTAL -> Row(
modifier = modifier.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(itemSpacingDp),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
verticalAlignment = crossContentAlignment.toVerticalAlignment(),
) {
items.forEach { GalleryChildItem(it, isHorizontal = true, crossContentAlignment) }
items.forEach {
ScrollableChildItem(
childDiv = it,
modifier = Modifier.fillMaxHeight(),
isHorizontal = true,
crossAxisAlignment = crossContentAlignment.toCrossAxisAlignment(),
)
}
}
DivGallery.Orientation.VERTICAL -> Column(
modifier = modifier.padding(contentPadding),
verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
verticalArrangement = Arrangement.spacedBy(itemSpacing),
horizontalAlignment = crossContentAlignment.toHorizontalAlignment(),
) {
items.forEach { GalleryChildItem(it, isHorizontal = false, crossContentAlignment) }
items.forEach {
ScrollableChildItem(
childDiv = it,
modifier = Modifier.fillMaxWidth(),
isHorizontal = false,
crossAxisAlignment = crossContentAlignment.toCrossAxisAlignment(),
)
}
}
}
}
@@ -28,11 +28,12 @@ internal fun Modifier.width(
): Modifier {
val align = horizontalAlignment?.toHorizontalAlignment() ?: Alignment.Start
return when (width) {
is DivSize.MatchParent -> fillMaxWidth()
is DivSize.MatchParent -> applySizeBounds(width.value.minSize, width.value.maxSize, isWidth = true)
.fillMaxWidth()
is DivSize.WrapContent -> {
val isConstrained = width.value.constrained?.observedValue() == true
wrapContentWidth(align = align, unbounded = !isConstrained)
.applyWrapContentWidthBounds(width)
.applySizeBounds(width.value.minSize, width.value.maxSize, isWidth = true)
}
is DivSize.Fixed -> wrapContentWidth(align = align, unbounded = true)
.requiredWidth(width.value.observedValue())
@@ -46,44 +47,33 @@ internal fun Modifier.height(
): Modifier {
val align = verticalAlignment?.toVerticalAlignment() ?: Alignment.Top
return when (height) {
is DivSize.MatchParent -> fillMaxHeight()
is DivSize.MatchParent -> applySizeBounds(height.value.minSize, height.value.maxSize, isWidth = false)
.fillMaxHeight()
is DivSize.WrapContent -> {
val isConstrained = height.value.constrained?.observedValue() == true
wrapContentHeight(align = align, unbounded = !isConstrained)
.applyWrapContentHeightBounds(height)
.applySizeBounds(height.value.minSize, height.value.maxSize, isWidth = false)
}
is DivSize.Fixed -> wrapContentHeight(align = align, unbounded = true)
.requiredHeight(height.value.observedValue())
}
}
@Composable
private fun Modifier.applyWrapContentWidthBounds(size: DivSize.WrapContent): Modifier {
val (minWidth, maxWidth) = size.wrapContentSizeBounds()
if (minWidth == null && maxWidth == null) return this
return widthIn(
min = minWidth ?: Dp.Unspecified,
max = maxWidth ?: Dp.Unspecified,
)
}
@Composable
private fun Modifier.applyWrapContentHeightBounds(size: DivSize.WrapContent): Modifier {
val (minHeight, maxHeight) = size.wrapContentSizeBounds()
if (minHeight == null && maxHeight == null) return this
return heightIn(
min = minHeight ?: Dp.Unspecified,
max = maxHeight ?: Dp.Unspecified,
)
}
@Composable
private fun DivSize.WrapContent.wrapContentSizeBounds(): Pair<Dp?, Dp?> {
val minSize = value.minSize?.toDpSize()
val maxSize = value.maxSize?.toDpSize()
if (minSize != null && maxSize != null && minSize > maxSize) {
return null to null
private fun Modifier.applySizeBounds(
minSize: DivSizeUnitValue?,
maxSize: DivSizeUnitValue?,
isWidth: Boolean,
): Modifier {
val min = minSize?.toDpSize()
val max = maxSize?.toDpSize()
if (min == null && max == null) return this
if (min != null && max != null && min > max) return this
return if (isWidth) {
widthIn(min = min ?: Dp.Unspecified, max = max ?: Dp.Unspecified)
} else {
heightIn(min = min ?: Dp.Unspecified, max = max ?: Dp.Unspecified)
}
return minSize to maxSize
}
@Composable
@@ -35,7 +35,11 @@ internal fun DivTextView(
val textStyle = data.observeTextStyle(fontSize, textAlignmentHorizontal, hyphens)
val maxLines = data.maxLines?.observedIntValue()?.coerceAtLeast(1) ?: Int.MAX_VALUE
val overflow = data.truncate.observedValue().toTextOverflow()
val overflow = if (data.maxLines != null) {
data.truncate.observedValue().toTextOverflow()
} else {
TextOverflow.Clip
}
val contentAlignment = toAlignment(textAlignmentHorizontal, textAlignmentVertical)
val annotatedString = buildAnnotatedText(text, data, fontSize)
@@ -146,7 +146,7 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada
val viewRenderer = Div2RendererFacade(binding.singleContainer, divContext, actionHandler)
val composeRenderer = ComposeRendererFacade(
binding.singleContainer,
binding.composeContainer,
divContext.divVariableController,
this
)
@@ -161,6 +161,8 @@ class Div2ScenarioActivity : AppCompatActivity(), Div2MetadataBottomSheet.Metada
binding.metadataButton,
binding.error,
binding.singleContainer,
binding.scrollView2,
binding.composeContainer,
divContext,
viewRenderer,
composeRenderer,
@@ -1,6 +1,7 @@
package com.yandex.divkit.demo.div.editor
import android.graphics.Bitmap
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
@@ -30,6 +31,8 @@ class DivEditorUi(
private val metadataButton: FloatingActionButton,
private val failedTextMessage: TextView,
private val divContainer: ViewGroup,
private val scrollView: View,
private val composeContainer: View,
private val divContext: Div2Context,
private val viewRenderer: DemoRendererFacade,
private val composeRenderer: DemoRendererFacade,
@@ -106,6 +109,8 @@ class DivEditorUi(
inactiveRenderer.deactivate()
activeRenderer.activate(lastDivData, lastDivDataTag)
activeRenderer.currentData?.let { adjustContainerHeight(it) }
scrollView.visibility = if (useComposeRenderer) GONE else VISIBLE
composeContainer.visibility = if (useComposeRenderer) VISIBLE else GONE
}
private fun adjustContainerHeight(divData: DivData) {
@@ -44,6 +44,12 @@
</LinearLayout>
</com.yandex.divkit.demo.div.DemoNestedScrollView>
<FrameLayout
android:id="@+id/compose_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<TextView
android:id="@+id/error"
style="@style/ActivitySubtitleStyle"