From 16f9fb2f40e0569c5c22de4a062cd890b4b64d4e Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sun, 30 Oct 2022 20:59:50 +0700 Subject: [PATCH] Rebase Scaffold fork (#8353) This adds content window insets supports that will be passed to all components used except top and bottom bar. --- .../CategoryFloatingActionButton.kt | 4 - .../kanade/presentation/components/AppBar.kt | 3 - .../presentation/components/Scaffold.kt | 118 ++++++++++-------- .../kanade/presentation/manga/MangaScreen.kt | 16 +-- .../manga/components/MangaToolbar.kt | 3 - 5 files changed, 75 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt index 2696815eea..72057f3d65 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt @@ -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(), ) } diff --git a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt index 5e155e9a25..4c5122e38e 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt b/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt index 156f87f002..e4e05d0ae9 100644 --- a/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt +++ b/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt @@ -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 /** * Material Design layout. @@ -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() - } else { - bottomBarHeight.toDp() - } - ) + fabHeight.toDp(), - start = insets.calculateLeftPadding((this@SubcomposeLayout).layoutDirection), - end = insets.calculateRightPadding((this@SubcomposeLayout).layoutDirection), + bottom = // Tachiyomi: Also take account of fab height when providing inner padding + if (bottomBarPlaceables.isEmpty() || bottomBarHeightPx == 0) { + max(insets.calculateBottomPadding(), fabOffsetDp) + } else { + 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)) } } } diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 8420279acd..626d65aa5e 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -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 @@ -214,8 +213,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 } @@ -277,8 +274,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()), ) } }, @@ -291,14 +286,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(), ), ) { @@ -434,7 +433,6 @@ fun MangaScreenLargeImpl( BackHandler(onBack = internalOnBackPressed) Scaffold( - modifier = Modifier.padding(insetPadding), topBar = { MangaToolbar( modifier = Modifier.onSizeChanged { topBarHeight = it.height }, @@ -490,13 +488,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 diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt index 28f9060114..9c92800ef3 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt @@ -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 @@ -200,7 +198,6 @@ fun MangaToolbar( } } }, - windowInsets = WindowInsets.statusBars, colors = TopAppBarDefaults.smallTopAppBarColors( containerColor = MaterialTheme.colorScheme .surfaceColorAtElevation(3.dp)