1 /*
<lambda>null2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settingslib.spa.widget.scaffold
18 
19 import androidx.compose.animation.core.AnimationSpec
20 import androidx.compose.animation.core.AnimationState
21 import androidx.compose.animation.core.CubicBezierEasing
22 import androidx.compose.animation.core.DecayAnimationSpec
23 import androidx.compose.animation.core.FastOutLinearInEasing
24 import androidx.compose.animation.core.animateDecay
25 import androidx.compose.animation.core.animateTo
26 import androidx.compose.foundation.gestures.Orientation
27 import androidx.compose.foundation.gestures.draggable
28 import androidx.compose.foundation.gestures.rememberDraggableState
29 import androidx.compose.foundation.layout.Arrangement
30 import androidx.compose.foundation.layout.Box
31 import androidx.compose.foundation.layout.Column
32 import androidx.compose.foundation.layout.Row
33 import androidx.compose.foundation.layout.RowScope
34 import androidx.compose.foundation.layout.WindowInsets
35 import androidx.compose.foundation.layout.WindowInsetsSides
36 import androidx.compose.foundation.layout.only
37 import androidx.compose.foundation.layout.padding
38 import androidx.compose.foundation.layout.safeDrawing
39 import androidx.compose.foundation.layout.windowInsetsPadding
40 import androidx.compose.material3.ExperimentalMaterial3Api
41 import androidx.compose.material3.LocalContentColor
42 import androidx.compose.material3.MaterialTheme
43 import androidx.compose.material3.ProvideTextStyle
44 import androidx.compose.material3.Text
45 import androidx.compose.material3.TopAppBarScrollBehavior
46 import androidx.compose.material3.TopAppBarState
47 import androidx.compose.runtime.Composable
48 import androidx.compose.runtime.CompositionLocalProvider
49 import androidx.compose.runtime.LaunchedEffect
50 import androidx.compose.runtime.NonRestartableComposable
51 import androidx.compose.runtime.Stable
52 import androidx.compose.runtime.derivedStateOf
53 import androidx.compose.runtime.getValue
54 import androidx.compose.runtime.mutableFloatStateOf
55 import androidx.compose.runtime.remember
56 import androidx.compose.ui.Alignment
57 import androidx.compose.ui.Modifier
58 import androidx.compose.ui.draw.clipToBounds
59 import androidx.compose.ui.draw.drawBehind
60 import androidx.compose.ui.graphics.Color
61 import androidx.compose.ui.graphics.graphicsLayer
62 import androidx.compose.ui.graphics.lerp
63 import androidx.compose.ui.input.pointer.pointerInput
64 import androidx.compose.ui.layout.AlignmentLine
65 import androidx.compose.ui.layout.LastBaseline
66 import androidx.compose.ui.layout.Layout
67 import androidx.compose.ui.layout.layoutId
68 import androidx.compose.ui.layout.onGloballyPositioned
69 import androidx.compose.ui.platform.LocalDensity
70 import androidx.compose.ui.semantics.clearAndSetSemantics
71 import androidx.compose.ui.semantics.heading
72 import androidx.compose.ui.semantics.isTraversalGroup
73 import androidx.compose.ui.semantics.semantics
74 import androidx.compose.ui.text.TextStyle
75 import androidx.compose.ui.text.style.TextOverflow
76 import androidx.compose.ui.unit.Constraints
77 import androidx.compose.ui.unit.Density
78 import androidx.compose.ui.unit.Dp
79 import androidx.compose.ui.unit.Velocity
80 import androidx.compose.ui.unit.dp
81 import com.android.settingslib.spa.framework.theme.SettingsDimension
82 import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
83 import com.android.settingslib.spa.framework.theme.settingsBackground
84 import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
85 import kotlin.math.abs
86 import kotlin.math.max
87 import kotlin.math.roundToInt
88 
89 private val safeDrawingWindowInsets: WindowInsets
90     @Composable
91     @NonRestartableComposable
92     get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
93 
94 @Composable
95 internal fun CustomizedTopAppBar(
96     title: @Composable () -> Unit,
97     navigationIcon: @Composable () -> Unit = {},
<lambda>null98     actions: @Composable RowScope.() -> Unit = {},
99 ) {
100     SingleRowTopAppBar(
101         title = title,
102         titleTextStyle = MaterialTheme.typography.titleMedium,
103         navigationIcon = navigationIcon,
104         actions = actions,
105         windowInsets = safeDrawingWindowInsets,
106         colors = topAppBarColors(),
107     )
108 }
109 
110 /** The customized LargeTopAppBar for Settings. */
111 @OptIn(ExperimentalMaterial3Api::class)
112 @Composable
CustomizedLargeTopAppBarnull113 internal fun CustomizedLargeTopAppBar(
114     title: String,
115     modifier: Modifier = Modifier,
116     navigationIcon: @Composable () -> Unit = {},
<lambda>null117     actions: @Composable RowScope.() -> Unit = {},
118     scrollBehavior: TopAppBarScrollBehavior? = null,
119 ) {
120     TwoRowsTopAppBar(
<lambda>null121         title = { Title(title = title, maxLines = 3) },
122         titleTextStyle =
123             if (isSpaExpressiveEnabled) MaterialTheme.typography.displaySmall.toSemiBoldWeight()
124             else MaterialTheme.typography.displaySmall,
125         smallTitleTextStyle =
126             if (isSpaExpressiveEnabled) MaterialTheme.typography.titleLarge.toSemiBoldWeight()
127             else MaterialTheme.typography.titleLarge,
128         titleBottomPadding = LargeTitleBottomPadding,
<lambda>null129         smallTitle = { Title(title = title, maxLines = 1) },
130         modifier = modifier,
131         navigationIcon = navigationIcon,
132         actions = actions,
133         colors = topAppBarColors(),
134         windowInsets = safeDrawingWindowInsets,
135         pinnedHeight = ContainerHeight,
136         scrollBehavior = scrollBehavior,
137     )
138 }
139 
140 @Composable
Titlenull141 private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
142     Text(
143         text = title,
144         modifier =
145             Modifier.padding(
146                     start =
147                         if (isSpaExpressiveEnabled) SettingsDimension.paddingExtraSmall
148                         else SettingsDimension.itemPaddingAround,
149                     end = SettingsDimension.itemPaddingEnd,
150                 )
151                 .semantics { heading() },
152         overflow = TextOverflow.Ellipsis,
153         maxLines = maxLines,
154     )
155 }
156 
157 @Composable
topAppBarColorsnull158 private fun topAppBarColors() =
159     TopAppBarColors(
160         containerColor = MaterialTheme.colorScheme.settingsBackground,
161         scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
162         navigationIconContentColor = MaterialTheme.colorScheme.onSurface,
163         titleContentColor = MaterialTheme.colorScheme.onSurface,
164         actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
165     )
166 
167 /**
168  * Represents the colors used by a top app bar in different states.
169  *
170  * This implementation animates the container color according to the top app bar scroll state. It
171  * does not animate the leading, headline, or trailing colors.
172  *
173  * @param containerColor the color used for the background of this BottomAppBar. Use
174  *   [Color.Transparent] to have no color.
175  * @param scrolledContainerColor the container color when content is scrolled behind it
176  * @param navigationIconContentColor the content color used for the navigation icon
177  * @param titleContentColor the content color used for the title
178  * @param actionIconContentColor the content color used for actions
179  * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a factory method
180  *   using the default material3 spec
181  */
182 @Stable
183 private class TopAppBarColors(
184     val containerColor: Color,
185     val scrolledContainerColor: Color,
186     val navigationIconContentColor: Color,
187     val titleContentColor: Color,
188     val actionIconContentColor: Color,
189 ) {
190 
191     /**
192      * Represents the container color used for the top app bar.
193      *
194      * A [colorTransitionFraction] provides a percentage value that can be used to generate a color.
195      * Usually, an app bar implementation will pass in a [colorTransitionFraction] read from the
196      * [TopAppBarState.collapsedFraction] or the [TopAppBarState.overlappedFraction].
197      *
198      * @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
199      *   percentage
200      */
201     @Stable
202     fun containerColor(colorTransitionFraction: Float): Color {
203         return lerp(
204             containerColor,
205             scrolledContainerColor,
206             FastOutLinearInEasing.transform(colorTransitionFraction),
207         )
208     }
209 
210     override fun equals(other: Any?): Boolean {
211         if (this === other) return true
212         if (other == null || other !is TopAppBarColors) return false
213 
214         if (containerColor != other.containerColor) return false
215         if (scrolledContainerColor != other.scrolledContainerColor) return false
216         if (navigationIconContentColor != other.navigationIconContentColor) return false
217         if (titleContentColor != other.titleContentColor) return false
218         if (actionIconContentColor != other.actionIconContentColor) return false
219 
220         return true
221     }
222 
223     override fun hashCode(): Int {
224         var result = containerColor.hashCode()
225         result = 31 * result + scrolledContainerColor.hashCode()
226         result = 31 * result + navigationIconContentColor.hashCode()
227         result = 31 * result + titleContentColor.hashCode()
228         result = 31 * result + actionIconContentColor.hashCode()
229 
230         return result
231     }
232 }
233 
234 /**
235  * A single-row top app bar that is designed to be called by the small and center aligned top app
236  * bar composables.
237  */
238 @Composable
SingleRowTopAppBarnull239 private fun SingleRowTopAppBar(
240     title: @Composable () -> Unit,
241     titleTextStyle: TextStyle,
242     navigationIcon: @Composable () -> Unit,
243     actions: @Composable (RowScope.() -> Unit),
244     windowInsets: WindowInsets,
245     colors: TopAppBarColors,
246 ) {
247     // Wrap the given actions in a Row.
248     val actionsRow =
249         @Composable {
250             Row(
251                 horizontalArrangement = Arrangement.End,
252                 verticalAlignment = Alignment.CenterVertically,
253                 content = actions,
254             )
255         }
256 
257     // Compose a Surface with a TopAppBarLayout content.
258     Box(
259         modifier =
260             Modifier.drawBehind { drawRect(color = colors.scrolledContainerColor) }
261                 .semantics { isTraversalGroup = true }
262                 .pointerInput(Unit) {}
263     ) {
264         val height = LocalDensity.current.run { ContainerHeight.toPx() }
265         TopAppBarLayout(
266             modifier =
267                 Modifier.windowInsetsPadding(windowInsets)
268                     // clip after padding so we don't show the title over the inset area
269                     .clipToBounds(),
270             heightPx = height,
271             navigationIconContentColor = colors.navigationIconContentColor,
272             titleContentColor = colors.titleContentColor,
273             actionIconContentColor = colors.actionIconContentColor,
274             title = title,
275             titleTextStyle = titleTextStyle,
276             titleAlpha = { 1f },
277             titleVerticalArrangement = Arrangement.Center,
278             titleBottomPadding = 0,
279             hideTitleSemantics = false,
280             navigationIcon = navigationIcon,
281             actions = actionsRow,
282             titleScaleDisabled = false,
283         )
284     }
285 }
286 
287 /**
288  * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
289  * composables.
290  *
291  * @throws [IllegalArgumentException] if the given [MaxHeightWithoutTitle] is equal or smaller than
292  *   the [pinnedHeight]
293  */
294 @OptIn(ExperimentalMaterial3Api::class)
295 @Composable
TwoRowsTopAppBarnull296 private fun TwoRowsTopAppBar(
297     modifier: Modifier = Modifier,
298     title: @Composable () -> Unit,
299     titleTextStyle: TextStyle,
300     titleBottomPadding: Dp,
301     smallTitle: @Composable () -> Unit,
302     smallTitleTextStyle: TextStyle,
303     navigationIcon: @Composable () -> Unit,
304     actions: @Composable RowScope.() -> Unit,
305     windowInsets: WindowInsets,
306     colors: TopAppBarColors,
307     pinnedHeight: Dp,
308     scrollBehavior: TopAppBarScrollBehavior?,
309 ) {
310     if (MaxHeightWithoutTitle <= pinnedHeight) {
311         throw IllegalArgumentException(
312             "A TwoRowsTopAppBar max height should be greater than its pinned height"
313         )
314     }
315     val pinnedHeightPx: Float
316     val titleBottomPaddingPx: Int
317     val defaultMaxHeightPx: Float
318     val density = LocalDensity.current
319     density.run {
320         pinnedHeightPx = pinnedHeight.toPx()
321         titleBottomPaddingPx = titleBottomPadding.roundToPx()
322         defaultMaxHeightPx = (MaxHeightWithoutTitle + DefaultTitleHeight).toPx()
323     }
324 
325     val maxHeightPx = remember(density) { mutableFloatStateOf(defaultMaxHeightPx) }
326 
327     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
328     // visible when collapsed.
329     scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
330     if (isSpaExpressiveEnabled) {
331         LaunchedEffect(scrollBehavior?.state?.heightOffsetLimit) { scrollBehavior?.collapse() }
332     }
333 
334     // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the
335     // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or
336     // collapse.
337     // This will potentially animate or interpolate a transition between the container color and the
338     // container's scrolled color according to the app bar's scroll state.
339     val colorTransitionFraction = { scrollBehavior?.state?.collapsedFraction ?: 0f }
340     val appBarContainerColor = { colors.containerColor(colorTransitionFraction()) }
341 
342     // Wrap the given actions in a Row.
343     val actionsRow =
344         @Composable {
345             Row(
346                 horizontalArrangement = Arrangement.End,
347                 verticalAlignment = Alignment.CenterVertically,
348                 content = actions,
349             )
350         }
351     val topTitleAlpha = { TopTitleAlphaEasing.transform(colorTransitionFraction()) }
352     val bottomTitleAlpha = { 1f - colorTransitionFraction() }
353     // Hide the top row title semantics when its alpha value goes below 0.5 threshold.
354     // Hide the bottom row title semantics when the top title semantics are active.
355     val hideTopRowSemantics by
356         remember(colorTransitionFraction) { derivedStateOf { colorTransitionFraction() < 0.5f } }
357     val hideBottomRowSemantics = !hideTopRowSemantics
358 
359     // Set up support for resizing the top app bar when vertically dragging the bar itself.
360     val appBarDragModifier =
361         if (scrollBehavior != null && !scrollBehavior.isPinned) {
362             Modifier.draggable(
363                 orientation = Orientation.Vertical,
364                 state =
365                     rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta },
366                 onDragStopped = { velocity ->
367                     settleAppBar(
368                         scrollBehavior.state,
369                         velocity,
370                         scrollBehavior.flingAnimationSpec,
371                         scrollBehavior.snapAnimationSpec,
372                     )
373                 },
374             )
375         } else {
376             Modifier
377         }
378 
379     Box(
380         modifier =
381             modifier
382                 .then(appBarDragModifier)
383                 .drawBehind { drawRect(color = appBarContainerColor()) }
384                 .semantics { isTraversalGroup = true }
385                 .pointerInput(Unit) {}
386     ) {
387         Column {
388             TopAppBarLayout(
389                 modifier =
390                     Modifier.windowInsetsPadding(windowInsets)
391                         // clip after padding so we don't show the title over the inset area
392                         .clipToBounds(),
393                 heightPx = pinnedHeightPx,
394                 navigationIconContentColor = colors.navigationIconContentColor,
395                 titleContentColor = colors.titleContentColor,
396                 actionIconContentColor = colors.actionIconContentColor,
397                 title = smallTitle,
398                 titleTextStyle = smallTitleTextStyle,
399                 titleAlpha = topTitleAlpha,
400                 titleVerticalArrangement = Arrangement.Center,
401                 titleBottomPadding = 0,
402                 hideTitleSemantics = hideTopRowSemantics,
403                 navigationIcon = navigationIcon,
404                 actions = actionsRow,
405             )
406             TopAppBarLayout(
407                 modifier =
408                     Modifier
409                         // only apply the horizontal sides of the window insets padding, since the
410                         // top
411                         // padding will always be applied by the layout above
412                         .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal))
413                         .clipToBounds(),
414                 heightPx =
415                     maxHeightPx.floatValue - pinnedHeightPx +
416                         (scrollBehavior?.state?.heightOffset ?: 0f),
417                 navigationIconContentColor = colors.navigationIconContentColor,
418                 titleContentColor = colors.titleContentColor,
419                 actionIconContentColor = colors.actionIconContentColor,
420                 title = {
421                     Box(
422                         modifier =
423                             Modifier.onGloballyPositioned { coordinates ->
424                                 val measuredMaxHeightPx =
425                                     density.run {
426                                         MaxHeightWithoutTitle.toPx() +
427                                             coordinates.size.height.toFloat() +
428                                             titleBaselineHeight.toPx()
429                                     }
430                                 // Allow larger max height for multi-line title, but do not reduce
431                                 // max height to prevent flaky.
432                                 if (measuredMaxHeightPx > defaultMaxHeightPx) {
433                                     maxHeightPx.floatValue = measuredMaxHeightPx
434                                 }
435                             }
436                     ) {
437                         title()
438                     }
439                 },
440                 titleTextStyle = titleTextStyle,
441                 titleAlpha = bottomTitleAlpha,
442                 titleVerticalArrangement = Arrangement.Bottom,
443                 titleBottomPadding = titleBottomPaddingPx,
444                 hideTitleSemantics = hideBottomRowSemantics,
445                 navigationIcon = {},
446                 actions = {},
447             )
448         }
449     }
450 }
451 
452 /**
453  * The base [Layout] for all top app bars. This function lays out a top app bar navigation icon
454  * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and
455  * the actions are optional.
456  *
457  * @param modifier a [Modifier]
458  * @param heightPx the total height this layout is capped to
459  * @param navigationIconContentColor the content color that will be applied via a
460  *   [LocalContentColor] when composing the navigation icon
461  * @param titleContentColor the color that will be applied via a [LocalContentColor] when composing
462  *   the title
463  * @param actionIconContentColor the content color that will be applied via a [LocalContentColor]
464  *   when composing the action icons
465  * @param title the top app bar title (header)
466  * @param titleTextStyle the title's text style
467  * @param modifier a [Modifier]
468  * @param titleAlpha the title's alpha
469  * @param titleVerticalArrangement the title's vertical arrangement
470  * @param titleBottomPadding the title's bottom padding
471  * @param hideTitleSemantics hides the title node from the semantic tree. Apply this boolean when
472  *   this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics from accessibility
473  *   services. This is needed to avoid having multiple titles visible to accessibility services at
474  *   the same time, when animating between collapsed / expanded states.
475  * @param navigationIcon a navigation icon [Composable]
476  * @param actions actions [Composable]
477  * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled.
478  */
479 @Composable
TopAppBarLayoutnull480 private fun TopAppBarLayout(
481     modifier: Modifier,
482     heightPx: Float,
483     navigationIconContentColor: Color,
484     titleContentColor: Color,
485     actionIconContentColor: Color,
486     title: @Composable () -> Unit,
487     titleTextStyle: TextStyle,
488     titleAlpha: () -> Float,
489     titleVerticalArrangement: Arrangement.Vertical,
490     titleBottomPadding: Int,
491     hideTitleSemantics: Boolean,
492     navigationIcon: @Composable () -> Unit,
493     actions: @Composable () -> Unit,
494     titleScaleDisabled: Boolean = true,
495 ) {
496     Layout(
497         {
498             Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) {
499                 CompositionLocalProvider(
500                     LocalContentColor provides navigationIconContentColor,
501                     content = navigationIcon,
502                 )
503             }
504             Box(
505                 Modifier.layoutId("title")
506                     .padding(horizontal = TopAppBarHorizontalPadding)
507                     .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier)
508                     .graphicsLayer { alpha = titleAlpha() }
509             ) {
510                 ProvideTextStyle(value = titleTextStyle) {
511                     CompositionLocalProvider(
512                         LocalContentColor provides titleContentColor,
513                         LocalDensity provides
514                             with(LocalDensity.current) {
515                                 Density(
516                                     density = density,
517                                     fontScale = if (titleScaleDisabled) 1f else fontScale,
518                                 )
519                             },
520                         content = title,
521                     )
522                 }
523             }
524             Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) {
525                 CompositionLocalProvider(
526                     LocalContentColor provides actionIconContentColor,
527                     content = actions,
528                 )
529             }
530         },
531         modifier = modifier,
532     ) { measurables, constraints ->
533         val navigationIconPlaceable =
534             measurables
535                 .first { it.layoutId == "navigationIcon" }
536                 .measure(constraints.copy(minWidth = 0))
537         val actionIconsPlaceable =
538             measurables
539                 .first { it.layoutId == "actionIcons" }
540                 .measure(constraints.copy(minWidth = 0))
541 
542         val maxTitleWidth =
543             if (constraints.maxWidth == Constraints.Infinity) {
544                 constraints.maxWidth
545             } else {
546                 (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width)
547                     .coerceAtLeast(0)
548             }
549         val titlePlaceable =
550             measurables
551                 .first { it.layoutId == "title" }
552                 .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth))
553 
554         // Locate the title's baseline.
555         val titleBaseline =
556             if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) {
557                 titlePlaceable[LastBaseline]
558             } else {
559                 0
560             }
561 
562         val layoutHeight = if (heightPx > 0) heightPx.roundToInt() else 0
563 
564         layout(constraints.maxWidth, layoutHeight) {
565             // Navigation icon
566             navigationIconPlaceable.placeRelative(
567                 x = 0,
568                 y = (layoutHeight - navigationIconPlaceable.height) / 2,
569             )
570 
571             // Title
572             titlePlaceable.placeRelative(
573                 x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width),
574                 y =
575                     when (titleVerticalArrangement) {
576                         Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
577                         // Apply bottom padding from the title's baseline only when the Arrangement
578                         // is "Bottom".
579                         Arrangement.Bottom ->
580                             if (titleBottomPadding == 0) layoutHeight - titlePlaceable.height
581                             else
582                                 layoutHeight -
583                                     titlePlaceable.height -
584                                     max(
585                                         0,
586                                         titleBottomPadding - titlePlaceable.height + titleBaseline,
587                                     )
588                         // Arrangement.Top
589                         else -> 0
590                     },
591             )
592 
593             // Action icons
594             actionIconsPlaceable.placeRelative(
595                 x = constraints.maxWidth - actionIconsPlaceable.width,
596                 y = (layoutHeight - actionIconsPlaceable.height) / 2,
597             )
598         }
599     }
600 }
601 
602 /**
603  * Settles the app bar by flinging, in case the given velocity is greater than zero, and snapping
604  * after the fling settles.
605  */
606 @OptIn(ExperimentalMaterial3Api::class)
settleAppBarnull607 private suspend fun settleAppBar(
608     state: TopAppBarState,
609     velocity: Float,
610     flingAnimationSpec: DecayAnimationSpec<Float>?,
611     snapAnimationSpec: AnimationSpec<Float>?,
612 ): Velocity {
613     // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
614     // and just return Zero Velocity.
615     // Note that we don't check for 0f due to float precision with the collapsedFraction
616     // calculation.
617     if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) {
618         return Velocity.Zero
619     }
620     var remainingVelocity = velocity
621     // In case there is an initial velocity that was left after a previous user fling, animate to
622     // continue the motion to expand or collapse the app bar.
623     if (flingAnimationSpec != null && abs(velocity) > 1f) {
624         var lastValue = 0f
625         AnimationState(initialValue = 0f, initialVelocity = velocity).animateDecay(
626             flingAnimationSpec
627         ) {
628             val delta = value - lastValue
629             val initialHeightOffset = state.heightOffset
630             state.heightOffset = initialHeightOffset + delta
631             val consumed = abs(initialHeightOffset - state.heightOffset)
632             lastValue = value
633             remainingVelocity = this.velocity
634             // avoid rounding errors and stop if anything is unconsumed
635             if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
636         }
637     }
638     // Snap if animation specs were provided.
639     if (snapAnimationSpec != null) {
640         if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) {
641             AnimationState(initialValue = state.heightOffset).animateTo(
642                 if (state.collapsedFraction < 0.5f) {
643                     0f
644                 } else {
645                     state.heightOffsetLimit
646                 },
647                 animationSpec = snapAnimationSpec,
648             ) {
649                 state.heightOffset = value
650             }
651         }
652     }
653 
654     return Velocity(0f, remainingVelocity)
655 }
656 
657 // An easing function used to compute the alpha value that is applied to the top title part of a
658 // Medium or Large app bar.
659 private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
660 
661 internal val MaxHeightWithoutTitle = if (isSpaExpressiveEnabled) 84.dp else 124.dp
662 internal val DefaultTitleHeight = 52.dp
663 internal val ContainerHeight = 56.dp
664 private val titleBaselineHeight = if (isSpaExpressiveEnabled) 8.dp else 0.dp
665 private val LargeTitleBottomPadding = 28.dp
666 private val TopAppBarHorizontalPadding = 4.dp
667 
668 // A title inset when the App-Bar is a Medium or Large one. Also used to size a spacer when the
669 // navigation icon is missing.
670 private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding
671