Rebase Scaffold fork (#8353)

This adds content window insets supports that will be passed to
all components used except top and bottom bar.
This commit is contained in:
Ivan Iskandar 2022-10-30 20:59:50 +07:00 committed by GitHub
parent 6bfaa85e84
commit 16f9fb2f40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 69 deletions

View File

@ -1,13 +1,11 @@
package eu.kanade.presentation.category.components package eu.kanade.presentation.category.components
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import eu.kanade.presentation.components.ExtendedFloatingActionButton import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.util.isScrolledToEnd import eu.kanade.presentation.util.isScrolledToEnd
@ -23,8 +21,6 @@ fun CategoryFloatingActionButton(
text = { Text(text = stringResource(R.string.action_add)) }, text = { Text(text = stringResource(R.string.action_add)) },
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") },
onClick = onCreate, onClick = onCreate,
modifier = Modifier
.navigationBarsPadding(),
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(), expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
) )
} }

View File

@ -4,9 +4,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -140,7 +138,6 @@ fun AppBar(
}, },
title = titleContent, title = titleContent,
actions = actions, actions = actions,
windowInsets = WindowInsets.statusBars,
colors = TopAppBarDefaults.smallTopAppBarColors( colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
elevation = if (isActionMode) 3.dp else 0.dp, elevation = if (isActionMode) 3.dp else 0.dp,

View File

@ -19,9 +19,11 @@ package eu.kanade.presentation.components
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
@ -37,6 +39,11 @@ import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxBy
import kotlin.math.max
/** /**
* <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>. * <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>.
@ -59,6 +66,7 @@ import androidx.compose.ui.unit.dp
* * Pass scroll behavior to top bar by default * * Pass scroll behavior to top bar by default
* * Remove height constraint for expanded app bar * * Remove height constraint for expanded app bar
* * Also take account of fab height when providing inner padding * * Also take account of fab height when providing inner padding
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
* *
* @param modifier the [Modifier] to be applied to this scaffold * @param modifier the [Modifier] to be applied to this scaffold
* @param topBar top app bar of the screen, typically a [SmallTopAppBar] * @param topBar top app bar of the screen, typically a [SmallTopAppBar]
@ -72,6 +80,9 @@ import androidx.compose.ui.unit.dp
* @param contentColor the preferred color for content inside this scaffold. Defaults to either the * @param contentColor the preferred color for content inside this scaffold. Defaults to either the
* matching content color for [containerColor], or to the current [LocalContentColor] if * matching content color for [containerColor], or to the current [LocalContentColor] if
* [containerColor] is not a color from the theme. * [containerColor] is not a color from the theme.
* @param contentWindowInsets window insets to be passed to content slot via PaddingValues params.
* Scaffold will take the insets into account from the top/bottom only if the topBar/ bottomBar
* are not present, as the scaffold expect topBar/bottomBar to handle insets instead
* @param content content of the screen. The lambda receives a [PaddingValues] that should be * @param content content of the screen. The lambda receives a [PaddingValues] that should be
* applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to * applied to the content root via [Modifier.padding] and [Modifier.consumeWindowInsets] to
* properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to * properly offset top and bottom bars. If using [Modifier.verticalScroll], apply this modifier to
@ -89,6 +100,7 @@ fun Scaffold(
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
containerColor: Color = MaterialTheme.colorScheme.background, containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor), contentColor: Color = contentColorFor(containerColor),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
) { ) {
androidx.compose.material3.Surface( androidx.compose.material3.Surface(
@ -104,6 +116,7 @@ fun Scaffold(
bottomBar = bottomBar, bottomBar = bottomBar,
content = content, content = content,
snackbar = snackbarHost, snackbar = snackbarHost,
contentWindowInsets = contentWindowInsets,
fab = floatingActionButton, fab = floatingActionButton,
) )
} }
@ -129,6 +142,7 @@ private fun ScaffoldLayout(
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
snackbar: @Composable () -> Unit, snackbar: @Composable () -> Unit,
fab: @Composable () -> Unit, fab: @Composable () -> Unit,
contentWindowInsets: WindowInsets,
bottomBar: @Composable () -> Unit, bottomBar: @Composable () -> Unit,
) { ) {
SubcomposeLayout { constraints -> SubcomposeLayout { constraints ->
@ -143,37 +157,51 @@ private fun ScaffoldLayout(
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity) val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
layout(layoutWidth, layoutHeight) { layout(layoutWidth, layoutHeight) {
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).map { val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
// Tachiyomi: layoutWidth after horizontal insets
val insetLayoutWidth = layoutWidth - leftInset - rightInset
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
it.measure(topBarConstraints) it.measure(topBarConstraints)
} }
val topBarHeight = topBarPlaceables.maxByOrNull { it.height }?.height ?: 0 val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).map { val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
it.measure(looseConstraints) it.measure(looseConstraints)
} }
val snackbarHeight = snackbarPlaceables.maxByOrNull { it.height }?.height ?: 0 val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
val snackbarWidth = snackbarPlaceables.maxByOrNull { it.width }?.width ?: 0 val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
// Tachiyomi: Calculate insets for snackbar placement offset
val snackbarLeft = if (snackbarPlaceables.isNotEmpty()) {
(insetLayoutWidth - snackbarWidth) / 2 + leftInset
} else {
0
}
val fabPlaceables = val fabPlaceables =
subcompose(ScaffoldLayoutContent.Fab, fab).mapNotNull { measurable -> subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
measurable.measure(looseConstraints).takeIf { it.height != 0 && it.width != 0 } measurable.measure(looseConstraints)
} }
val fabHeight = fabPlaceables.maxByOrNull { it.height }?.height ?: 0 val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0
val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0
val fabPlacement = if (fabPlaceables.isNotEmpty()) { val fabPlacement = if (fabPlaceables.isNotEmpty() && fabWidth != 0 && fabHeight != 0) {
val fabWidth = fabPlaceables.maxByOrNull { it.width }!!.width
// FAB distance from the left of the layout, taking into account LTR / RTL // FAB distance from the left of the layout, taking into account LTR / RTL
// Tachiyomi: Calculate insets for fab placement offset
val fabLeftOffset = if (fabPosition == FabPosition.End) { val fabLeftOffset = if (fabPosition == FabPosition.End) {
if (layoutDirection == LayoutDirection.Ltr) { if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth layoutWidth - FabSpacing.roundToPx() - fabWidth - rightInset
} else { } else {
FabSpacing.roundToPx() FabSpacing.roundToPx() + leftInset
} }
} else { } else {
(layoutWidth - fabWidth) / 2 leftInset + ((insetLayoutWidth - fabWidth) / 2)
} }
FabPlacement( FabPlacement(
@ -190,75 +218,63 @@ private fun ScaffoldLayout(
LocalFabPlacement provides fabPlacement, LocalFabPlacement provides fabPlacement,
content = bottomBar, content = bottomBar,
) )
}.map { it.measure(looseConstraints) } }.fastMap { it.measure(looseConstraints) }
val bottomBarHeight = bottomBarPlaceables.maxByOrNull { it.height }?.height ?: 0 val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
val fabOffsetFromBottom = fabPlacement?.let { val fabOffsetFromBottom = fabPlacement?.let {
if (bottomBarHeight == 0) { max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
it.height + FabSpacing.roundToPx()
} else {
// Total height is the bottom bar height + the FAB height + the padding
// between the FAB and bottom bar
bottomBarHeight + it.height + FabSpacing.roundToPx()
}
} }
val snackbarOffsetFromBottom = if (snackbarHeight != 0) { val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight) snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
} else { } else {
0 0
} }
/**
* Tachiyomi: Also take account of fab height when providing inner padding
*/
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) { val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
val insets = WindowInsets.Companion.safeDrawing val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
.asPaddingValues(this@SubcomposeLayout) val fabOffsetDp = fabOffsetFromBottom?.toDp() ?: 0.dp
val bottomBarHeightPx = bottomBarHeight ?: 0
val innerPadding = PaddingValues( val innerPadding = PaddingValues(
top = top =
if (topBarHeight == 0) { if (topBarPlaceables.isEmpty()) {
insets.calculateTopPadding() insets.calculateTopPadding()
} else { } else {
topBarHeight.toDp() topBarHeight.toDp()
}, },
bottom = bottom = // Tachiyomi: Also take account of fab height when providing inner padding
( if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) {
if (bottomBarHeight == 0) { max(insets.calculateBottomPadding(), fabOffsetDp)
insets.calculateBottomPadding() } else {
} else { max(bottomBarHeightPx.toDp(), fabOffsetDp)
bottomBarHeight.toDp() },
} start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
) + fabHeight.toDp(), end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
start = insets.calculateLeftPadding((this@SubcomposeLayout).layoutDirection),
end = insets.calculateRightPadding((this@SubcomposeLayout).layoutDirection),
) )
content(innerPadding) content(innerPadding)
}.map { it.measure(looseConstraints) } }.fastMap { it.measure(looseConstraints) }
// Placing to control drawing order to match default elevation of each placeable // Placing to control drawing order to match default elevation of each placeable
bodyContentPlaceables.forEach { bodyContentPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
topBarPlaceables.forEach { topBarPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
snackbarPlaceables.forEach { snackbarPlaceables.fastForEach {
it.place( it.place(
(layoutWidth - snackbarWidth) / 2, snackbarLeft,
layoutHeight - snackbarOffsetFromBottom, layoutHeight - snackbarOffsetFromBottom,
) )
} }
// The bottom bar is always at the bottom of the layout // The bottom bar is always at the bottom of the layout
bottomBarPlaceables.forEach { bottomBarPlaceables.fastForEach {
it.place(0, layoutHeight - bottomBarHeight) it.place(0, layoutHeight - (bottomBarHeight ?: 0))
} }
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
fabPlacement?.let { placement -> fabPlaceables.fastForEach {
fabPlaceables.forEach { it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
}
} }
} }
} }

View File

@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
@ -214,8 +213,6 @@ private fun MangaScreenSmallImpl(
BackHandler(onBack = internalOnBackPressed) BackHandler(onBack = internalOnBackPressed)
Scaffold( Scaffold(
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal).asPaddingValues()),
topBar = { topBar = {
val firstVisibleItemIndex by remember { val firstVisibleItemIndex by remember {
derivedStateOf { chapterListState.firstVisibleItemIndex } derivedStateOf { chapterListState.firstVisibleItemIndex }
@ -277,8 +274,6 @@ private fun MangaScreenSmallImpl(
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading, onClick = onContinueReading,
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
) )
} }
}, },
@ -291,14 +286,18 @@ private fun MangaScreenSmallImpl(
enabled = chapters.none { it.selected }, enabled = chapters.none { it.selected },
indicatorPadding = contentPadding, indicatorPadding = contentPadding,
) { ) {
val layoutDirection = LocalLayoutDirection.current
VerticalFastScroller( VerticalFastScroller(
listState = chapterListState, listState = chapterListState,
topContentPadding = topPadding, topContentPadding = topPadding,
endContentPadding = contentPadding.calculateEndPadding(layoutDirection),
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxHeight(), modifier = Modifier.fillMaxHeight(),
state = chapterListState, state = chapterListState,
contentPadding = PaddingValues( contentPadding = PaddingValues(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
bottom = contentPadding.calculateBottomPadding(), bottom = contentPadding.calculateBottomPadding(),
), ),
) { ) {
@ -434,7 +433,6 @@ fun MangaScreenLargeImpl(
BackHandler(onBack = internalOnBackPressed) BackHandler(onBack = internalOnBackPressed)
Scaffold( Scaffold(
modifier = Modifier.padding(insetPadding),
topBar = { topBar = {
MangaToolbar( MangaToolbar(
modifier = Modifier.onSizeChanged { topBarHeight = it.height }, modifier = Modifier.onSizeChanged { topBarHeight = it.height },
@ -490,13 +488,15 @@ fun MangaScreenLargeImpl(
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading, onClick = onContinueReading,
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
modifier = Modifier
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
) )
} }
}, },
) { contentPadding -> ) { contentPadding ->
TwoPanelBox( TwoPanelBox(
modifier = Modifier.padding(
start = contentPadding.calculateStartPadding(layoutDirection),
end = contentPadding.calculateEndPadding(layoutDirection),
),
startContent = { startContent = {
Column( Column(
modifier = Modifier modifier = Modifier

View File

@ -2,8 +2,6 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
@ -200,7 +198,6 @@ fun MangaToolbar(
} }
} }
}, },
windowInsets = WindowInsets.statusBars,
colors = TopAppBarDefaults.smallTopAppBarColors( colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme containerColor = MaterialTheme.colorScheme
.surfaceColorAtElevation(3.dp) .surfaceColorAtElevation(3.dp)