PM-12668: Update TopAppBar divider (#4060)

This commit is contained in:
David Perez 2024-10-10 15:05:53 -05:00 committed by GitHub
parent 537281f6c3
commit 22c0745993
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 121 additions and 14 deletions

View file

@ -56,6 +56,24 @@ fun Modifier.scrolledContainerBackground(
) )
} }
/**
* Adds a bottom divider specified by the given [topAppBarScrollBehavior] and its current scroll
* state.
*/
@OmitFromCoverage
@OptIn(ExperimentalMaterial3Api::class)
@Stable
@Composable
fun Modifier.scrolledContainerBottomDivider(
topAppBarScrollBehavior: TopAppBarScrollBehavior,
enabled: Boolean = true,
): Modifier =
this.bottomDivider(
alpha = topAppBarScrollBehavior.toScrolledContainerDividerAlpha(),
enabled = enabled,
thickness = (0.5).dp,
)
/** /**
* This is a [Modifier] extension for drawing a divider at the bottom of the composable. * This is a [Modifier] extension for drawing a divider at the bottom of the composable.
*/ */
@ -68,11 +86,13 @@ fun Modifier.bottomDivider(
thickness: Dp = DividerDefaults.Thickness, thickness: Dp = DividerDefaults.Thickness,
color: Color = BitwardenTheme.colorScheme.stroke.divider, color: Color = BitwardenTheme.colorScheme.stroke.divider,
enabled: Boolean = true, enabled: Boolean = true,
alpha: Float = 1f,
): Modifier = drawWithCache { ): Modifier = drawWithCache {
onDrawWithContent { onDrawWithContent {
drawContent() drawContent()
if (enabled) { if (enabled) {
drawLine( drawLine(
alpha = alpha,
color = color, color = color,
strokeWidth = thickness.toPx(), strokeWidth = thickness.toPx(),
start = Offset( start = Offset(

View file

@ -5,6 +5,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.util.lerp
/** /**
* Returns the correct color for a scrolled container based on the given [TopAppBarScrollBehavior] * Returns the correct color for a scrolled container based on the given [TopAppBarScrollBehavior]
@ -28,3 +29,22 @@ fun TopAppBarScrollBehavior.toScrolledContainerColor(
fraction = FastOutLinearInEasing.transform(progressFraction), fraction = FastOutLinearInEasing.transform(progressFraction),
) )
} }
/**
* Returns the correct alpha, as a [Float], for a containers alpha based on the given
* [TopAppBarScrollBehavior].
*/
@OptIn(ExperimentalMaterial3Api::class)
fun TopAppBarScrollBehavior.toScrolledContainerDividerAlpha(): Float {
val progressFraction = if (this.isPinned) {
this.state.overlappedFraction
} else {
this.state.collapsedFraction
}
return lerp(
start = 0f,
stop = 1f,
// The easing function here matches what is currently in TopAppBarColors.containerColor
fraction = FastOutLinearInEasing.transform(fraction = progressFraction),
)
}

View file

@ -11,7 +11,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@ -27,6 +30,8 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
* @param title The text to be displayed as the title of the app bar. * @param title The text to be displayed as the title of the app bar.
* @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar * @param scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar
* behaves in conjunction with scrolling content. * behaves in conjunction with scrolling content.
* @param isBottomDividerEnabled Determines if the bottom divider should be displayed on scroll or
* not.
* @param actions A lambda containing the set of actions (usually icons or similar) to display * @param actions A lambda containing the set of actions (usually icons or similar) to display
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in * in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions. * defining the layout of the actions.
@ -37,6 +42,7 @@ fun BitwardenMediumTopAppBar(
title: String, title: String,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isBottomDividerEnabled: Boolean = true,
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
) { ) {
MediumTopAppBar( MediumTopAppBar(
@ -46,10 +52,20 @@ fun BitwardenMediumTopAppBar(
Text( Text(
text = title, text = title,
style = BitwardenTheme.typography.titleLarge, style = BitwardenTheme.typography.titleLarge,
modifier = Modifier.testTag("PageTitleLabel"), modifier = Modifier.testTag(tag = "PageTitleLabel"),
) )
}, },
modifier = modifier.testTag("HeaderBarComponent"), modifier = modifier
.testTag(tag = "HeaderBarComponent")
.scrolledContainerBottomDivider(
topAppBarScrollBehavior = scrollBehavior,
enabled = isBottomDividerEnabled,
)
// When the scrolling divider is disabled, we show the static divider
.bottomDivider(
enabled = !isBottomDividerEnabled,
thickness = (0.5).dp,
),
actions = actions, actions = actions,
) )
} }

View file

@ -17,7 +17,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.Dp
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
import com.x8bit.bitwarden.ui.platform.base.util.tabNavigation import com.x8bit.bitwarden.ui.platform.base.util.tabNavigation
import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors
@ -45,7 +47,9 @@ fun BitwardenSearchTopAppBar(
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
TopAppBar( TopAppBar(
modifier = modifier.testTag("HeaderBarComponent"), modifier = modifier
.testTag(tag = "HeaderBarComponent")
.bottomDivider(thickness = Dp.Hairline),
colors = bitwardenTopAppBarColors(), colors = bitwardenTopAppBarColors(),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {

View file

@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl import com.x8bit.bitwarden.ui.platform.base.util.mirrorIfRtl
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors import com.x8bit.bitwarden.ui.platform.components.appbar.color.bitwardenTopAppBarColors
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
@ -113,10 +114,12 @@ fun BitwardenTopAppBar(
text = title, text = title,
style = BitwardenTheme.typography.titleLarge, style = BitwardenTheme.typography.titleLarge,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag("PageTitleLabel"), modifier = Modifier.testTag(tag = "PageTitleLabel"),
) )
}, },
modifier = modifier.testTag("HeaderBarComponent"), modifier = modifier
.testTag(tag = "HeaderBarComponent")
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
actions = actions, actions = actions,
) )
} else { } else {
@ -131,13 +134,15 @@ fun BitwardenTopAppBar(
maxLines = 1, maxLines = 1,
softWrap = false, softWrap = false,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag("PageTitleLabel"), modifier = Modifier.testTag(tag = "PageTitleLabel"),
onTextLayout = { onTextLayout = {
titleTextHasOverflow = it.hasVisualOverflow titleTextHasOverflow = it.hasVisualOverflow
}, },
) )
}, },
modifier = modifier.testTag("HeaderBarComponent"), modifier = modifier
.testTag(tag = "HeaderBarComponent")
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
actions = actions, actions = actions,
) )
} }

View file

@ -23,7 +23,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenSearchTopAppBar import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenSearchTopAppBar
import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon import com.x8bit.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.x8bit.bitwarden.ui.platform.components.content.BitwardenErrorContent import com.x8bit.bitwarden.ui.platform.components.content.BitwardenErrorContent
@ -91,9 +90,7 @@ fun SearchScreen(
navigationIconContentDescription = stringResource(id = R.string.back), navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = searchHandlers.onBackClick, onNavigationIconClick = searchHandlers.onBackClick,
), ),
modifier = Modifier modifier = Modifier.testTag(tag = "SearchFieldEntry"),
.testTag("SearchFieldEntry")
.bottomDivider(),
) )
}, },
modifier = Modifier modifier = Modifier

View file

@ -18,8 +18,8 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.bottomDivider
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBackground import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBackground
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenSelectionRow import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenSelectionRow
@ -69,8 +69,8 @@ fun VaultFilter(
Row( Row(
modifier = Modifier modifier = Modifier
.scrolledContainerBackground(topAppBarScrollBehavior) .scrolledContainerBackground(topAppBarScrollBehavior = topAppBarScrollBehavior)
.bottomDivider() .scrolledContainerBottomDivider(topAppBarScrollBehavior = topAppBarScrollBehavior)
.padding(vertical = 8.dp) .padding(vertical = 8.dp)
.testTag("ActiveFilterRow") .testTag("ActiveFilterRow")
.then(modifier), .then(modifier),

View file

@ -219,6 +219,7 @@ private fun VaultScreenScaffold(
BitwardenMediumTopAppBar( BitwardenMediumTopAppBar(
title = state.appBarTitle(), title = state.appBarTitle(),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
isBottomDividerEnabled = state.vaultFilterDataWithFilter == null,
actions = { actions = {
BitwardenAccountActionItem( BitwardenAccountActionItem(
initials = state.initials, initials = state.initials,

View file

@ -89,4 +89,48 @@ class TopAppBarScrollBehaviorExtensionsTest {
), ),
) )
} }
@Suppress("MaxLineLength")
@Test
fun `toScrolledContainerDividerAlpha for pinned states should interpolate based on the overlappedFraction`() {
var overlappedFraction = 0f
val topAppBarScrollBehavior = mockk<TopAppBarScrollBehavior> {
every { isPinned } returns true
every { state.overlappedFraction } answers { overlappedFraction }
}
overlappedFraction = 0f
assertEquals(
overlappedFraction,
topAppBarScrollBehavior.toScrolledContainerDividerAlpha(),
)
overlappedFraction = 1f
assertEquals(
overlappedFraction,
topAppBarScrollBehavior.toScrolledContainerDividerAlpha(),
)
}
@Suppress("MaxLineLength")
@Test
fun `toScrolledContainerDividerAlpha for pinned states should interpolate based on the collapsedFraction`() {
var collapsedFraction = 0f
val topAppBarScrollBehavior = mockk<TopAppBarScrollBehavior> {
every { isPinned } returns false
every { state.collapsedFraction } answers { collapsedFraction }
}
collapsedFraction = 0f
assertEquals(
collapsedFraction,
topAppBarScrollBehavior.toScrolledContainerDividerAlpha(),
)
collapsedFraction = 1f
assertEquals(
collapsedFraction,
topAppBarScrollBehavior.toScrolledContainerDividerAlpha(),
)
}
} }