Browse Source

Adjust insets handling in tablet UI (#8711)

* Adds startBar slot in Scaffold to handle nav rail
* Consumes unneeded insets in settings
Ivan Iskandar 2 years ago
parent
commit
ca500da4d8

+ 20 - 5
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<FabPlacement?> { 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 }

+ 15 - 4
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,
         )
     }

+ 42 - 44
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()
+                                }
+                            },
+                        )
                     }
                 }
             }

+ 11 - 0
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)