Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eb114442 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update responsive grid layout + alpha animation" into main

parents 799131ca 545ff286
Loading
Loading
Loading
Loading
+56 −1
Original line number Diff line number Diff line
@@ -151,6 +151,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -947,12 +948,28 @@ private fun BoxScope.CommunalHubLazyGrid(
                    }
                }
            } else {
                val itemAlpha =
                    if (communalResponsiveGrid()) {
                        val percentVisible by
                            remember(gridState, index) {
                                derivedStateOf { calculatePercentVisible(gridState, index) }
                            }
                        animateFloatAsState(percentVisible)
                    } else {
                        null
                    }

                CommunalContent(
                    model = item,
                    viewModel = viewModel,
                    size = size,
                    selected = false,
                    modifier = Modifier.requiredSize(dpSize).animateItem(),
                    modifier =
                        Modifier.requiredSize(dpSize).animateItem().thenIf(
                            communalResponsiveGrid()
                        ) {
                            Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f }
                        },
                    index = index,
                    contentListState = contentListState,
                    interactionHandler = interactionHandler,
@@ -1856,6 +1873,44 @@ private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) =
        size.span
    }

private fun IntRect.percentOverlap(other: IntRect): Float {
    val intersection = intersect(other)
    if (intersection.width < 0 || intersection.height < 0) {
        return 0f
    }
    val overlapArea = intersection.width * intersection.height
    val area = width * height
    return overlapArea.toFloat() / area.toFloat()
}

private fun calculatePercentVisible(state: LazyGridState, index: Int): Float {
    val viewportSize = state.layoutInfo.viewportSize
    val visibleRect =
        IntRect(
            offset =
                IntOffset(
                    state.layoutInfo.viewportStartOffset + state.layoutInfo.beforeContentPadding,
                    0,
                ),
            size =
                IntSize(
                    width =
                        viewportSize.width -
                            state.layoutInfo.beforeContentPadding -
                            state.layoutInfo.afterContentPadding,
                    height = viewportSize.height,
                ),
        )

    val itemInfo = state.layoutInfo.visibleItemsInfo.find { it.index == index }
    return if (itemInfo != null) {
        val boundingBox = IntRect(itemInfo.offset, itemInfo.size)
        boundingBox.percentOverlap(visibleRect)
    } else {
        0f
    }
}

private object Colors {
    val DisabledColorFilter by lazy { disabledColorMatrix() }

+42 −10
Original line number Diff line number Diff line
@@ -36,7 +36,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.toComposeRect
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
@@ -45,6 +47,7 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.coerceAtMost
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.times
import androidx.window.layout.WindowMetricsCalculator

/**
 * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain
@@ -71,7 +74,7 @@ fun ResponsiveLazyHorizontalGrid(
            "$minHorizontalArrangement and $minVerticalArrangement, respectively."
    }
    BoxWithConstraints(modifier) {
        val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight)
        val gridSize = rememberGridSize()
        val layoutDirection = LocalLayoutDirection.current
        val density = LocalDensity.current

@@ -128,25 +131,43 @@ fun ResponsiveLazyHorizontalGrid(
        val extraWidth = maxWidth - usedWidth
        val extraHeight = maxHeight - usedHeight

        val finalStartPadding = minStartPadding + extraWidth / 2
        // If there is a single column or single row, distribute extra space evenly across the grid.
        // Otherwise, distribute it along the content padding to center the content.
        val distributeHorizontalSpaceAlongGutters = gridSize.height == 1 || gridSize.width == 1
        val evenlyDistributedWidth =
            if (distributeHorizontalSpaceAlongGutters) {
                extraWidth / (gridSize.width + 1)
            } else {
                extraWidth / 2
            }

        val finalStartPadding = minStartPadding + evenlyDistributedWidth
        val finalEndPadding = minEndPadding + evenlyDistributedWidth
        val finalTopPadding = minTopPadding + extraHeight / 2

        val finalContentPadding =
            PaddingValues(
                start = finalStartPadding,
                end = minEndPadding + extraWidth / 2,
                end = finalEndPadding,
                top = finalTopPadding,
                bottom = minBottomPadding + extraHeight / 2,
            )

        with(density) { setContentOffset(Offset(finalStartPadding.toPx(), finalTopPadding.toPx())) }

        val horizontalArrangement =
            if (distributeHorizontalSpaceAlongGutters) {
                minHorizontalArrangement + evenlyDistributedWidth
            } else {
                minHorizontalArrangement
            }

        LazyHorizontalGrid(
            rows = GridCells.Fixed(gridSize.height),
            modifier = Modifier.fillMaxSize(),
            state = state,
            contentPadding = finalContentPadding,
            horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement),
            horizontalArrangement = Arrangement.spacedBy(horizontalArrangement),
            verticalArrangement = Arrangement.spacedBy(minVerticalArrangement),
            flingBehavior = flingBehavior,
            userScrollEnabled = userScrollEnabled,
@@ -210,27 +231,38 @@ data class SizeInfo(
}

@Composable
private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize {
private fun rememberGridSize(): IntSize {
    val configuration = LocalConfiguration.current
    val orientation = configuration.orientation
    val screenSize = calculateWindowSize()

    return remember(orientation, maxWidth, maxHeight) {
    return remember(orientation, screenSize) {
        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            IntSize(
                width = calculateNumCellsWidth(maxWidth),
                height = calculateNumCellsHeight(maxHeight),
                width = calculateNumCellsWidth(screenSize.width),
                height = calculateNumCellsHeight(screenSize.height),
            )
        } else {
            // In landscape we invert the rows/columns to ensure we match the same area as portrait.
            // This keeps the number of elements in the grid consistent when changing orientation.
            IntSize(
                width = calculateNumCellsHeight(maxWidth),
                height = calculateNumCellsWidth(maxHeight),
                width = calculateNumCellsHeight(screenSize.width),
                height = calculateNumCellsWidth(screenSize.height),
            )
        }
    }
}

@Composable
fun calculateWindowSize(): DpSize {
    // Observe view configuration changes and recalculate the size class on each change.
    LocalConfiguration.current
    val density = LocalDensity.current
    val context = LocalContext.current
    val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
    return with(density) { metrics.bounds.toComposeRect().size.toDpSize() }
}

private fun calculateNumCellsWidth(width: Dp) =
    // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes
    when {