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