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

Commit e45635f1 authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Fix CustomizedAppBarTest" into main

parents 80f314c7 1a7ecd8f
Loading
Loading
Loading
Loading
+180 −157
Original line number Diff line number Diff line
@@ -41,23 +41,25 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.TopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.Layout
@@ -66,6 +68,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
@@ -80,11 +83,10 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt

private val windowInsets: WindowInsets
private val safeDrawingWindowInsets: WindowInsets
    @Composable
    @NonRestartableComposable
    get() = WindowInsets.safeDrawing
        .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
    get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)

@Composable
internal fun CustomizedTopAppBar(
@@ -97,14 +99,12 @@ internal fun CustomizedTopAppBar(
        titleTextStyle = MaterialTheme.typography.titleMedium,
        navigationIcon = navigationIcon,
        actions = actions,
        windowInsets = windowInsets,
        windowInsets = safeDrawingWindowInsets,
        colors = topAppBarColors(),
    )
}

/**
 * The customized LargeTopAppBar for Settings.
 */
/** The customized LargeTopAppBar for Settings. */
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun CustomizedLargeTopAppBar(
@@ -124,7 +124,7 @@ internal fun CustomizedLargeTopAppBar(
        navigationIcon = navigationIcon,
        actions = actions,
        colors = topAppBarColors(),
        windowInsets = windowInsets,
        windowInsets = safeDrawingWindowInsets,
        pinnedHeight = ContainerHeight,
        scrollBehavior = scrollBehavior,
    )
@@ -134,7 +134,8 @@ internal fun CustomizedLargeTopAppBar(
private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
    Text(
        text = title,
        modifier = Modifier.padding(
        modifier =
            Modifier.padding(
                    start = SettingsDimension.itemPaddingAround,
                    end = SettingsDimension.itemPaddingEnd,
                )
@@ -145,7 +146,8 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
}

@Composable
private fun topAppBarColors() = TopAppBarColors(
private fun topAppBarColors() =
    TopAppBarColors(
        containerColor = MaterialTheme.colorScheme.settingsBackground,
        scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
        navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
@@ -155,17 +157,18 @@ private fun topAppBarColors() = TopAppBarColors(

/**
 * Represents the colors used by a top app bar in different states.
 *
 * This implementation animates the container color according to the top app bar scroll state. It
 * does not animate the leading, headline, or trailing colors.
 *
 * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a
 * factory method using the default material3 spec
 * @param containerColor the color used for the background of this BottomAppBar. Use
 *   [Color.Transparent] to have no color.
 * @param scrolledContainerColor the container color when content is scrolled behind it
 * @param navigationIconContentColor the content color used for the navigation icon
 * @param titleContentColor the content color used for the title
 * @param actionIconContentColor the content color used for actions
 * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a factory method
 *   using the default material3 spec
 */
@Stable
private class TopAppBarColors(
@@ -180,8 +183,8 @@ private class TopAppBarColors(
     * Represents the container color used for the top app bar.
     *
     * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from
     * the [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
     * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from the
     * [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
     *
     * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
     *   percentage
@@ -233,7 +236,8 @@ private fun SingleRowTopAppBar(
    colors: TopAppBarColors,
) {
    // Wrap the given actions in a Row.
    val actionsRow = @Composable {
    val actionsRow =
        @Composable {
            Row(
                horizontalArrangement = Arrangement.End,
                verticalAlignment = Alignment.CenterVertically,
@@ -242,11 +246,16 @@ private fun SingleRowTopAppBar(
        }

    // Compose a Surface with a TopAppBarLayout content.
    Surface(color = colors.scrolledContainerColor) {
    Box(
        modifier =
            Modifier.drawBehind { drawRect(color = colors.scrolledContainerColor) }
                .semantics { isTraversalGroup = true }
                .pointerInput(Unit) {}
    ) {
        val height = LocalDensity.current.run { ContainerHeight.toPx() }
        TopAppBarLayout(
            modifier = Modifier
                .windowInsetsPadding(windowInsets)
            modifier =
                Modifier.windowInsetsPadding(windowInsets)
                    // clip after padding so we don't show the title over the inset area
                    .clipToBounds(),
            heightPx = height,
@@ -255,7 +264,7 @@ private fun SingleRowTopAppBar(
            actionIconContentColor = colors.actionIconContentColor,
            title = title,
            titleTextStyle = titleTextStyle,
            titleAlpha = 1f,
            titleAlpha = { 1f },
            titleVerticalArrangement = Arrangement.Center,
            titleBottomPadding = 0,
            hideTitleSemantics = false,
@@ -308,42 +317,40 @@ private fun TwoRowsTopAppBar(

    // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
    // visible when collapsed.
    SideEffect {
        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) {
    scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
        }
    }

    // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
    // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
    // collapse.
    // This will potentially animate or interpolate a transition between the container color and the
    // container's scrolled color according to the app bar's scroll state.
    val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
    val appBarContainerColor = colors.containerColor(colorTransitionFraction)
    val colorTransitionFraction = { scrollBehavior?.state?.collapsedFraction ?: 0f }
    val appBarContainerColor = { colors.containerColor(colorTransitionFraction()) }

    // Wrap the given actions in a Row.
    val actionsRow = @Composable {
    val actionsRow =
        @Composable {
            Row(
                horizontalArrangement = Arrangement.End,
                verticalAlignment = Alignment.CenterVertically,
                content = actions
            )
        }
    val topTitleAlpha = TopTitleAlphaEasing.transform(colorTransitionFraction)
    val bottomTitleAlpha = 1f - colorTransitionFraction
    val topTitleAlpha = { TopTitleAlphaEasing.transform(colorTransitionFraction()) }
    val bottomTitleAlpha = { 1f - colorTransitionFraction() }
    // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
    // Hide the bottom row title semantics when the top title semantics are active.
    val hideTopRowSemantics = colorTransitionFraction < 0.5f
    val hideTopRowSemantics by
        remember(colorTransitionFraction) { derivedStateOf { colorTransitionFraction() < 0.5f } }
    val hideBottomRowSemantics = !hideTopRowSemantics

    // Set up support for resizing the top app bar when vertically dragging the bar itself.
    val appBarDragModifier = if (scrollBehavior != null && !scrollBehavior.isPinned) {
    val appBarDragModifier =
        if (scrollBehavior != null && !scrollBehavior.isPinned) {
            Modifier.draggable(
                orientation = Orientation.Vertical,
            state = rememberDraggableState { delta ->
                scrollBehavior.state.heightOffset += delta
            },
                state =
                    rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta },
                onDragStopped = { velocity ->
                    settleAppBar(
                        scrollBehavior.state,
@@ -357,11 +364,18 @@ private fun TwoRowsTopAppBar(
            Modifier
        }

    Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
    Box(
        modifier =
            modifier
                .then(appBarDragModifier)
                .drawBehind { drawRect(color = appBarContainerColor()) }
                .semantics { isTraversalGroup = true }
                .pointerInput(Unit) {}
    ) {
        Column {
            TopAppBarLayout(
                modifier = Modifier
                    .windowInsetsPadding(windowInsets)
                modifier =
                    Modifier.windowInsetsPadding(windowInsets)
                        // clip after padding so we don't show the title over the inset area
                        .clipToBounds(),
                heightPx = pinnedHeightPx,
@@ -378,27 +392,37 @@ private fun TwoRowsTopAppBar(
                actions = actionsRow,
            )
            TopAppBarLayout(
                modifier = Modifier
                    // only apply the horizontal sides of the window insets padding, since the top
                modifier =
                    Modifier
                        // only apply the horizontal sides of the window insets padding, since the
                        // top
                        // padding will always be applied by the layout above
                        .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal))
                        .clipToBounds(),
                heightPx = maxHeightPx.floatValue - pinnedHeightPx +
                heightPx =
                    maxHeightPx.floatValue - pinnedHeightPx +
                        (scrollBehavior?.state?.heightOffset ?: 0f),
                navigationIconContentColor = colors.navigationIconContentColor,
                titleContentColor = colors.titleContentColor,
                actionIconContentColor = colors.actionIconContentColor,
                title = {
                    Box(modifier = Modifier.onGloballyPositioned { coordinates ->
                        val measuredMaxHeightPx = density.run {
                            MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
                    Box(
                        modifier =
                            Modifier.onGloballyPositioned { coordinates ->
                                val measuredMaxHeightPx =
                                    density.run {
                                        MaxHeightWithoutTitle.toPx() +
                                            coordinates.size.height.toFloat()
                                    }
                                // Allow larger max height for multi-line title, but do not reduce
                                // max height to prevent flaky.
                                if (measuredMaxHeightPx > defaultMaxHeightPx) {
                                    maxHeightPx.floatValue = measuredMaxHeightPx
                                }
                    }) { title() }
                            }
                    ) {
                        title()
                    }
                },
                titleTextStyle = titleTextStyle,
                titleAlpha = bottomTitleAlpha,
@@ -431,10 +455,10 @@ private fun TwoRowsTopAppBar(
 * @param titleAlpha the title's alpha
 * @param titleVerticalArrangement the title's vertical arrangement
 * @param titleBottomPadding the title's bottom padding
 * @param hideTitleSemantics hides the title node from the semantic tree. Apply this
 * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
 * from accessibility services. This is needed to avoid having multiple titles visible to
 * accessibility services at the same time, when animating between collapsed / expanded states.
 * @param hideTitleSemantics hides the title node from the semantic tree. Apply this boolean when
 *   this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics from accessibility
 *   services. This is needed to avoid having multiple titles visible to accessibility services at
 *   the same time, when animating between collapsed / expanded states.
 * @param navigationIcon a navigation icon [Composable]
 * @param actions actions [Composable]
 * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled.
@@ -448,7 +472,7 @@ private fun TopAppBarLayout(
    actionIconContentColor: Color,
    title: @Composable () -> Unit,
    titleTextStyle: TextStyle,
    titleAlpha: Float,
    titleAlpha: () -> Float,
    titleVerticalArrangement: Arrangement.Vertical,
    titleBottomPadding: Int,
    hideTitleSemantics: Boolean,
@@ -458,27 +482,23 @@ private fun TopAppBarLayout(
) {
    Layout(
        {
            Box(
                Modifier
                    .layoutId("navigationIcon")
                    .padding(start = TopAppBarHorizontalPadding)
            ) {
            Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) {
                CompositionLocalProvider(
                    LocalContentColor provides navigationIconContentColor,
                    content = navigationIcon
                )
            }
            Box(
                Modifier
                    .layoutId("title")
                Modifier.layoutId("title")
                    .padding(horizontal = TopAppBarHorizontalPadding)
                    .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier)
                    .graphicsLayer(alpha = titleAlpha)
                    .graphicsLayer { alpha = titleAlpha() }
            ) {
                ProvideTextStyle(value = titleTextStyle) {
                    CompositionLocalProvider(
                        LocalContentColor provides titleContentColor,
                        LocalDensity provides with(LocalDensity.current) {
                        LocalDensity provides
                            with(LocalDensity.current) {
                                Density(
                                    density = density,
                                    fontScale = if (titleScaleDisabled) 1f else fontScale,
@@ -488,11 +508,7 @@ private fun TopAppBarLayout(
                    )
                }
            }
            Box(
                Modifier
                    .layoutId("actionIcons")
                    .padding(end = TopAppBarHorizontalPadding)
            ) {
            Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) {
                CompositionLocalProvider(
                    LocalContentColor provides actionIconContentColor,
                    content = actions
@@ -502,20 +518,24 @@ private fun TopAppBarLayout(
        modifier = modifier
    ) { measurables, constraints ->
        val navigationIconPlaceable =
            measurables.first { it.layoutId == "navigationIcon" }
            measurables
                .first { it.layoutId == "navigationIcon" }
                .measure(constraints.copy(minWidth = 0))
        val actionIconsPlaceable =
            measurables.first { it.layoutId == "actionIcons" }
            measurables
                .first { it.layoutId == "actionIcons" }
                .measure(constraints.copy(minWidth = 0))

        val maxTitleWidth = if (constraints.maxWidth == Constraints.Infinity) {
        val maxTitleWidth =
            if (constraints.maxWidth == Constraints.Infinity) {
                constraints.maxWidth
            } else {
                (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
                    .coerceAtLeast(0)
            }
        val titlePlaceable =
            measurables.first { it.layoutId == "title" }
            measurables
                .first { it.layoutId == "title" }
                .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))

        // Locate the title's baseline.
@@ -538,13 +558,17 @@ private fun TopAppBarLayout(
            // Title
            titlePlaceable.placeRelative(
                x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width),
                y = when (titleVerticalArrangement) {
                y =
                    when (titleVerticalArrangement) {
                        Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
                    // Apply bottom padding from the title's baseline only when the Arrangement is
                    // "Bottom".
                        // Apply bottom padding from the title's baseline only when the Arrangement
                        // is "Bottom".
                        Arrangement.Bottom ->
                            if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
                        else layoutHeight - titlePlaceable.height - max(
                            else
                                layoutHeight -
                                    titlePlaceable.height -
                                    max(
                                        0,
                                        titleBottomPadding - titlePlaceable.height + titleBaseline
                                    )
@@ -562,7 +586,6 @@ private fun TopAppBarLayout(
    }
}


/**
 * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
 * after the fling settles.
@@ -603,9 +626,7 @@ private suspend fun settleAppBar(
    }
    // Snap if animation specs were provided.
    if (snapAnimationSpec != null) {
        if (state.heightOffset < 0 &&
            state.heightOffset > state.heightOffsetLimit
        ) {
        if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) {
            AnimationState(initialValue = state.heightOffset).animateTo(
                if (state.collapsedFraction < 0.5f) {
                    0f
@@ -613,7 +634,9 @@ private suspend fun settleAppBar(
                    state.heightOffsetLimit
                },
                animationSpec = snapAnimationSpec
            ) { state.heightOffset = value }
            ) {
                state.heightOffset = value
            }
        }
    }

+19 −240

File changed.

Preview size limit exceeded, changes collapsed.