Rebase Scaffold fork ()

This adds content window insets supports that will be passed to
all components used except top and bottom bar.

(cherry picked from commit 16f9fb2f40e0569c5c22de4a062cd890b4b64d4e)
This commit is contained in:
Ivan Iskandar 2022-10-30 20:59:50 +07:00 committed by Jobobby04
parent 86afa2ee6a
commit 1256646e91
5 changed files with 75 additions and 69 deletions
app/src/main/java/eu/kanade/presentation

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

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

@ -19,9 +19,11 @@ package eu.kanade.presentation.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
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.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
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.LayoutDirection
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>.
@ -59,6 +66,7 @@ import androidx.compose.ui.unit.dp
* * Pass scroll behavior to top bar by default
* * Remove height constraint for expanded app bar
* * 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 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
* matching content color for [containerColor], or to the current [LocalContentColor] if
* [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
* 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
@ -89,6 +100,7 @@ fun Scaffold(
floatingActionButtonPosition: FabPosition = FabPosition.End,
containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor),
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
content: @Composable (PaddingValues) -> Unit,
) {
androidx.compose.material3.Surface(
@ -104,6 +116,7 @@ fun Scaffold(
bottomBar = bottomBar,
content = content,
snackbar = snackbarHost,
contentWindowInsets = contentWindowInsets,
fab = floatingActionButton,
)
}
@ -129,6 +142,7 @@ private fun ScaffoldLayout(
content: @Composable (PaddingValues) -> Unit,
snackbar: @Composable () -> Unit,
fab: @Composable () -> Unit,
contentWindowInsets: WindowInsets,
bottomBar: @Composable () -> Unit,
) {
SubcomposeLayout { constraints ->
@ -143,37 +157,51 @@ private fun ScaffoldLayout(
val topBarConstraints = looseConstraints.copy(maxHeight = Constraints.Infinity)
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)
}
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)
}
val snackbarHeight = snackbarPlaceables.maxByOrNull { it.height }?.height ?: 0
val snackbarWidth = snackbarPlaceables.maxByOrNull { it.width }?.width ?: 0
val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 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 =
subcompose(ScaffoldLayoutContent.Fab, fab).mapNotNull { measurable ->
measurable.measure(looseConstraints).takeIf { it.height != 0 && it.width != 0 }
subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
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 fabWidth = fabPlaceables.maxByOrNull { it.width }!!.width
val fabPlacement = if (fabPlaceables.isNotEmpty() && fabWidth != 0 && fabHeight != 0) {
// 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) {
if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth
layoutWidth - FabSpacing.roundToPx() - fabWidth - rightInset
} else {
FabSpacing.roundToPx()
FabSpacing.roundToPx() + leftInset
}
} else {
(layoutWidth - fabWidth) / 2
leftInset + ((insetLayoutWidth - fabWidth) / 2)
}
FabPlacement(
@ -190,75 +218,63 @@ private fun ScaffoldLayout(
LocalFabPlacement provides fabPlacement,
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 {
if (bottomBarHeight == 0) {
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()
}
max(bottomBarHeight ?: 0, bottomInset) + it.height + FabSpacing.roundToPx()
}
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight)
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight ?: bottomInset)
} else {
0
}
/**
* Tachiyomi: Also take account of fab height when providing inner padding
*/
val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
val insets = WindowInsets.Companion.safeDrawing
.asPaddingValues(this@SubcomposeLayout)
val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
val fabOffsetDp = fabOffsetFromBottom?.toDp() ?: 0.dp
val bottomBarHeightPx = bottomBarHeight ?: 0
val innerPadding = PaddingValues(
top =
if (topBarHeight == 0) {
if (topBarPlaceables.isEmpty()) {
insets.calculateTopPadding()
} else {
topBarHeight.toDp()
},
bottom =
(
if (bottomBarHeight == 0) {
insets.calculateBottomPadding()
bottom = // Tachiyomi: Also take account of fab height when providing inner padding
if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) {
max(insets.calculateBottomPadding(), fabOffsetDp)
} else {
bottomBarHeight.toDp()
}
) + fabHeight.toDp(),
start = insets.calculateLeftPadding((this@SubcomposeLayout).layoutDirection),
end = insets.calculateRightPadding((this@SubcomposeLayout).layoutDirection),
max(bottomBarHeightPx.toDp(), fabOffsetDp)
},
start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
)
content(innerPadding)
}.map { it.measure(looseConstraints) }
}.fastMap { it.measure(looseConstraints) }
// Placing to control drawing order to match default elevation of each placeable
bodyContentPlaceables.forEach {
bodyContentPlaceables.fastForEach {
it.place(0, 0)
}
topBarPlaceables.forEach {
topBarPlaceables.fastForEach {
it.place(0, 0)
}
snackbarPlaceables.forEach {
snackbarPlaceables.fastForEach {
it.place(
(layoutWidth - snackbarWidth) / 2,
snackbarLeft,
layoutHeight - snackbarOffsetFromBottom,
)
}
// The bottom bar is always at the bottom of the layout
bottomBarPlaceables.forEach {
it.place(0, layoutHeight - bottomBarHeight)
bottomBarPlaceables.fastForEach {
it.place(0, layoutHeight - (bottomBarHeight ?: 0))
}
// Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
fabPlacement?.let { placement ->
fabPlaceables.forEach {
it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
}
fabPlaceables.fastForEach {
it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
}
}
}

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

@ -2,8 +2,6 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.layout.Box
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.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close
@ -246,7 +244,6 @@ fun MangaToolbar(
}
}
},
windowInsets = WindowInsets.statusBars,
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme
.surfaceColorAtElevation(3.dp)