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.
*/
@ -68,11 +86,13 @@ fun Modifier.bottomDivider(
thickness: Dp = DividerDefaults.Thickness,
color: Color = BitwardenTheme.colorScheme.stroke.divider,
enabled: Boolean = true,
alpha: Float = 1f,
): Modifier = drawWithCache {
onDrawWithContent {
drawContent()
if (enabled) {
drawLine(
alpha = alpha,
color = color,
strokeWidth = thickness.toPx(),
start = Offset(

View file

@ -5,6 +5,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.ui.graphics.Color
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]
@ -28,3 +29,22 @@ fun TopAppBarScrollBehavior.toScrolledContainerColor(
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.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.button.BitwardenStandardIconButton
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 scrollBehavior Defines the scrolling behavior of the app bar. It controls how the app bar
* 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
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
* defining the layout of the actions.
@ -37,6 +42,7 @@ fun BitwardenMediumTopAppBar(
title: String,
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
isBottomDividerEnabled: Boolean = true,
actions: @Composable RowScope.() -> Unit = {},
) {
MediumTopAppBar(
@ -46,10 +52,20 @@ fun BitwardenMediumTopAppBar(
Text(
text = title,
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,
)
}

View file

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

View file

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

View file

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

View file

@ -18,8 +18,8 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.scrolledContainerBottomDivider
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.row.BitwardenSelectionRow
@ -69,8 +69,8 @@ fun VaultFilter(
Row(
modifier = Modifier
.scrolledContainerBackground(topAppBarScrollBehavior)
.bottomDivider()
.scrolledContainerBackground(topAppBarScrollBehavior = topAppBarScrollBehavior)
.scrolledContainerBottomDivider(topAppBarScrollBehavior = topAppBarScrollBehavior)
.padding(vertical = 8.dp)
.testTag("ActiveFilterRow")
.then(modifier),

View file

@ -219,6 +219,7 @@ private fun VaultScreenScaffold(
BitwardenMediumTopAppBar(
title = state.appBarTitle(),
scrollBehavior = scrollBehavior,
isBottomDividerEnabled = state.vaultFilterDataWithFilter == null,
actions = {
BitwardenAccountActionItem(
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(),
)
}
}