mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Adjust insets handling in tablet UI (#8711)
* Adds startBar slot in Scaffold to handle nav rail * Consumes unneeded insets in settings
This commit is contained in:
		@@ -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 }
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user