From ca500da4d899815112eadaa6a5f79c9b5e5766af Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 10 Dec 2022 22:02:13 +0700 Subject: [PATCH] Adjust insets handling in tablet UI (#8711) * Adds startBar slot in Scaffold to handle nav rail * Consumes unneeded insets in settings --- .../presentation/components/Scaffold.kt | 25 ++++-- .../presentation/components/TwoPanelBox.kt | 19 +++- .../eu/kanade/tachiyomi/ui/home/HomeScreen.kt | 86 +++++++++---------- .../tachiyomi/ui/setting/SettingsScreen.kt | 11 +++ 4 files changed, 88 insertions(+), 53 deletions(-) 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 de708bcd5..047544487 100644 --- a/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt +++ b/app/src/main/java/eu/kanade/presentation/components/Scaffold.kt @@ -23,7 +23,7 @@ import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.exclude -import androidx.compose.foundation.layout.withConsumedWindowInsets +import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ScaffoldDefaults @@ -72,9 +72,11 @@ import kotlin.math.max * * Also take account of fab height when providing inner padding * * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used * * Handle consumed window insets + * * Add startBar slot for Navigation Rail * * @param modifier the [Modifier] to be applied to this scaffold * @param topBar top app bar of the screen, typically a [SmallTopAppBar] + * @param startBar side bar on the start of the screen, typically a [NavigationRail] * @param bottomBar bottom bar of the screen, typically a [NavigationBar] * @param snackbarHost component to host [Snackbar]s that are pushed to be shown via * [SnackbarHostState.showSnackbar], typically a [SnackbarHost] @@ -100,6 +102,7 @@ fun Scaffold( topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {}, bottomBar: @Composable () -> Unit = {}, + startBar: @Composable () -> Unit = {}, snackbarHost: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {}, floatingActionButtonPosition: FabPosition = FabPosition.End, @@ -113,7 +116,7 @@ fun Scaffold( androidx.compose.material3.Surface( modifier = Modifier .nestedScroll(topBarScrollBehavior.nestedScrollConnection) - .withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) } + .onConsumedWindowInsetsChanged { remainingWindowInsets.insets = contentWindowInsets.exclude(it) } .then(modifier), color = containerColor, contentColor = contentColor, @@ -121,6 +124,7 @@ fun Scaffold( ScaffoldLayout( fabPosition = floatingActionButtonPosition, topBar = { topBar(topBarScrollBehavior) }, + startBar = startBar, bottomBar = bottomBar, content = content, snackbar = snackbarHost, @@ -147,6 +151,7 @@ fun Scaffold( private fun ScaffoldLayout( fabPosition: FabPosition, topBar: @Composable () -> Unit, + startBar: @Composable () -> Unit, content: @Composable (PaddingValues) -> Unit, snackbar: @Composable () -> Unit, fab: @Composable () -> Unit, @@ -168,8 +173,15 @@ private fun ScaffoldLayout( val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection) val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection) val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout) + + // Tachiyomi: Add startBar slot for Navigation Rail + val startBarPlaceables = subcompose(ScaffoldLayoutContent.StartBar, startBar).fastMap { + it.measure(looseConstraints) + } + val startBarWidth = startBarPlaceables.fastMaxBy { it.width }?.width ?: 0 + // Tachiyomi: layoutWidth after horizontal insets - val insetLayoutWidth = layoutWidth - leftInset - rightInset + val insetLayoutWidth = layoutWidth - leftInset - rightInset - startBarWidth val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap { it.measure(topBarConstraints) @@ -256,7 +268,7 @@ private fun ScaffoldLayout( } else { max(bottomBarHeightPx.toDp(), fabOffsetDp) }, - start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection), + start = max(insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection), startBarWidth.toDp()), end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection), ) content(innerPadding) @@ -267,6 +279,9 @@ private fun ScaffoldLayout( bodyContentPlaceables.fastForEach { it.place(0, 0) } + startBarPlaceables.fastForEach { + it.placeRelative(0, 0) + } topBarPlaceables.fastForEach { it.place(0, 0) } @@ -339,4 +354,4 @@ internal val LocalFabPlacement = staticCompositionLocalOf { null // FAB spacing above the bottom bar / bottom of the Scaffold private val FabSpacing = 16.dp -private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, Fab, BottomBar } +private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, Fab, BottomBar, StartBar } diff --git a/app/src/main/java/eu/kanade/presentation/components/TwoPanelBox.kt b/app/src/main/java/eu/kanade/presentation/components/TwoPanelBox.kt index 37d5a1f83..e9b509508 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TwoPanelBox.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TwoPanelBox.kt @@ -3,32 +3,43 @@ package eu.kanade.presentation.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.dp @Composable fun TwoPanelBox( modifier: Modifier = Modifier, + contentWindowInsets: WindowInsets = WindowInsets(0), startContent: @Composable BoxScope.() -> Unit, endContent: @Composable BoxScope.() -> Unit, ) { + val direction = LocalLayoutDirection.current + val padding = contentWindowInsets.asPaddingValues() + val startPadding = padding.calculateStartPadding(direction) + val endPadding = padding.calculateEndPadding(direction) BoxWithConstraints(modifier = modifier.fillMaxSize()) { - val firstWidth = (maxWidth / 2).coerceAtMost(450.dp) - val secondWidth = maxWidth - firstWidth + val width = maxWidth - startPadding - endPadding + val firstWidth = (width / 2).coerceAtMost(450.dp) + val secondWidth = width - firstWidth Box( modifier = Modifier .align(Alignment.TopStart) - .width(firstWidth), + .width(firstWidth + startPadding), content = startContent, ) Box( modifier = Modifier .align(Alignment.TopEnd) - .width(secondWidth), + .width(secondWidth + endPadding), content = endContent, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt index 5f781b2ac..8522d15f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -7,10 +7,9 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.animation.with import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.consumedWindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.padding import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox @@ -25,7 +24,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.semantics.contentDescription @@ -85,53 +83,53 @@ object HomeScreen : Screen { ) { tabNavigator -> // Provide usable navigator to content screen CompositionLocalProvider(LocalNavigator provides navigator) { - Row(verticalAlignment = Alignment.CenterVertically) { - if (isTabletUi()) { - NavigationRail { - tabs.fastForEach { - NavigationRailItem(it) + Scaffold( + startBar = { + if (isTabletUi()) { + NavigationRail { + tabs.fastForEach { + NavigationRailItem(it) + } } } - } - Scaffold( - bottomBar = { - if (!isTabletUi()) { - val bottomNavVisible by produceState(initialValue = true) { - showBottomNavEvent.receiveAsFlow().collectLatest { value = it } - } - AnimatedVisibility( - visible = bottomNavVisible, - enter = expandVertically(), - exit = shrinkVertically(), - ) { - NavigationBar { - tabs.fastForEach { - NavigationBarItem(it) - } + }, + bottomBar = { + if (!isTabletUi()) { + val bottomNavVisible by produceState(initialValue = true) { + showBottomNavEvent.receiveAsFlow().collectLatest { value = it } + } + AnimatedVisibility( + visible = bottomNavVisible, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + NavigationBar { + tabs.fastForEach { + NavigationBarItem(it) } } } - }, - contentWindowInsets = WindowInsets(0), - ) { contentPadding -> - Box( - modifier = Modifier - .padding(contentPadding) - .consumedWindowInsets(contentPadding), - ) { - AnimatedContent( - targetState = tabNavigator.current, - transitionSpec = { - materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) with - materialFadeThroughOut(durationMillis = TabFadeDuration) - }, - content = { - tabNavigator.saveableState(key = "currentTab", it) { - it.Content() - } - }, - ) } + }, + contentWindowInsets = WindowInsets(0), + ) { contentPadding -> + Box( + modifier = Modifier + .padding(contentPadding) + .consumeWindowInsets(contentPadding), + ) { + AnimatedContent( + targetState = tabNavigator.current, + transitionSpec = { + materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) with + materialFadeThroughOut(durationMillis = TabFadeDuration) + }, + content = { + tabNavigator.saveableState(key = "currentTab", it) { + it.Content() + } + }, + ) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt index 1fc9672d1..2ae7e3c98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsScreen.kt @@ -1,7 +1,14 @@ package eu.kanade.tachiyomi.ui.setting +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator @@ -55,7 +62,11 @@ class SettingsScreen private constructor( SettingsGeneralScreen }, ) { + val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal) TwoPanelBox( + modifier = Modifier + .windowInsetsPadding(insets) + .consumeWindowInsets(insets), startContent = { CompositionLocalProvider(LocalBackPress provides parentNavigator::pop) { SettingsMainScreen.Content(twoPane = true)