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:
Ivan Iskandar 2022-12-10 22:02:13 +07:00 committed by GitHub
parent 820ed6a468
commit ca500da4d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 53 deletions

View File

@ -23,7 +23,7 @@ import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.exclude 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.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScaffoldDefaults import androidx.compose.material3.ScaffoldDefaults
@ -72,9 +72,11 @@ import kotlin.math.max
* * Also take account of fab height when providing inner padding * * Also take account of fab height when providing inner padding
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used * * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
* * Handle consumed window insets * * Handle consumed window insets
* * Add startBar slot for Navigation Rail
* *
* @param modifier the [Modifier] to be applied to this scaffold * @param modifier the [Modifier] to be applied to this scaffold
* @param topBar top app bar of the screen, typically a [SmallTopAppBar] * @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 bottomBar bottom bar of the screen, typically a [NavigationBar]
* @param snackbarHost component to host [Snackbar]s that are pushed to be shown via * @param snackbarHost component to host [Snackbar]s that are pushed to be shown via
* [SnackbarHostState.showSnackbar], typically a [SnackbarHost] * [SnackbarHostState.showSnackbar], typically a [SnackbarHost]
@ -100,6 +102,7 @@ fun Scaffold(
topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {}, topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {},
bottomBar: @Composable () -> Unit = {}, bottomBar: @Composable () -> Unit = {},
startBar: @Composable () -> Unit = {},
snackbarHost: @Composable () -> Unit = {}, snackbarHost: @Composable () -> Unit = {},
floatingActionButton: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
@ -113,7 +116,7 @@ fun Scaffold(
androidx.compose.material3.Surface( androidx.compose.material3.Surface(
modifier = Modifier modifier = Modifier
.nestedScroll(topBarScrollBehavior.nestedScrollConnection) .nestedScroll(topBarScrollBehavior.nestedScrollConnection)
.withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) } .onConsumedWindowInsetsChanged { remainingWindowInsets.insets = contentWindowInsets.exclude(it) }
.then(modifier), .then(modifier),
color = containerColor, color = containerColor,
contentColor = contentColor, contentColor = contentColor,
@ -121,6 +124,7 @@ fun Scaffold(
ScaffoldLayout( ScaffoldLayout(
fabPosition = floatingActionButtonPosition, fabPosition = floatingActionButtonPosition,
topBar = { topBar(topBarScrollBehavior) }, topBar = { topBar(topBarScrollBehavior) },
startBar = startBar,
bottomBar = bottomBar, bottomBar = bottomBar,
content = content, content = content,
snackbar = snackbarHost, snackbar = snackbarHost,
@ -147,6 +151,7 @@ fun Scaffold(
private fun ScaffoldLayout( private fun ScaffoldLayout(
fabPosition: FabPosition, fabPosition: FabPosition,
topBar: @Composable () -> Unit, topBar: @Composable () -> Unit,
startBar: @Composable () -> Unit,
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
snackbar: @Composable () -> Unit, snackbar: @Composable () -> Unit,
fab: @Composable () -> Unit, fab: @Composable () -> Unit,
@ -168,8 +173,15 @@ private fun ScaffoldLayout(
val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection) val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection) val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout) 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 // Tachiyomi: layoutWidth after horizontal insets
val insetLayoutWidth = layoutWidth - leftInset - rightInset val insetLayoutWidth = layoutWidth - leftInset - rightInset - startBarWidth
val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap { val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
it.measure(topBarConstraints) it.measure(topBarConstraints)
@ -256,7 +268,7 @@ private fun ScaffoldLayout(
} else { } else {
max(bottomBarHeightPx.toDp(), fabOffsetDp) 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), end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection),
) )
content(innerPadding) content(innerPadding)
@ -267,6 +279,9 @@ private fun ScaffoldLayout(
bodyContentPlaceables.fastForEach { bodyContentPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
startBarPlaceables.fastForEach {
it.placeRelative(0, 0)
}
topBarPlaceables.fastForEach { topBarPlaceables.fastForEach {
it.place(0, 0) it.place(0, 0)
} }
@ -339,4 +354,4 @@ internal val LocalFabPlacement = staticCompositionLocalOf<FabPlacement?> { null
// FAB spacing above the bottom bar / bottom of the Scaffold // FAB spacing above the bottom bar / bottom of the Scaffold
private val FabSpacing = 16.dp private val FabSpacing = 16.dp
private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, Fab, BottomBar } private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, Fab, BottomBar, StartBar }

View File

@ -3,32 +3,43 @@ package eu.kanade.presentation.components
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints 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.fillMaxSize
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@Composable @Composable
fun TwoPanelBox( fun TwoPanelBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentWindowInsets: WindowInsets = WindowInsets(0),
startContent: @Composable BoxScope.() -> Unit, startContent: @Composable BoxScope.() -> Unit,
endContent: @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()) { BoxWithConstraints(modifier = modifier.fillMaxSize()) {
val firstWidth = (maxWidth / 2).coerceAtMost(450.dp) val width = maxWidth - startPadding - endPadding
val secondWidth = maxWidth - firstWidth val firstWidth = (width / 2).coerceAtMost(450.dp)
val secondWidth = width - firstWidth
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.width(firstWidth), .width(firstWidth + startPadding),
content = startContent, content = startContent,
) )
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.width(secondWidth), .width(secondWidth + endPadding),
content = endContent, content = endContent,
) )
} }

View File

@ -7,10 +7,9 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.with import androidx.compose.animation.with
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets 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.foundation.layout.padding
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox import androidx.compose.material3.BadgedBox
@ -25,7 +24,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
@ -85,53 +83,53 @@ object HomeScreen : Screen {
) { tabNavigator -> ) { tabNavigator ->
// Provide usable navigator to content screen // Provide usable navigator to content screen
CompositionLocalProvider(LocalNavigator provides navigator) { CompositionLocalProvider(LocalNavigator provides navigator) {
Row(verticalAlignment = Alignment.CenterVertically) { Scaffold(
if (isTabletUi()) { startBar = {
NavigationRail { if (isTabletUi()) {
tabs.fastForEach { NavigationRail {
NavigationRailItem(it) tabs.fastForEach {
NavigationRailItem(it)
}
} }
} }
} },
Scaffold( bottomBar = {
bottomBar = { if (!isTabletUi()) {
if (!isTabletUi()) { val bottomNavVisible by produceState(initialValue = true) {
val bottomNavVisible by produceState(initialValue = true) { showBottomNavEvent.receiveAsFlow().collectLatest { value = it }
showBottomNavEvent.receiveAsFlow().collectLatest { value = it } }
} AnimatedVisibility(
AnimatedVisibility( visible = bottomNavVisible,
visible = bottomNavVisible, enter = expandVertically(),
enter = expandVertically(), exit = shrinkVertically(),
exit = shrinkVertically(), ) {
) { NavigationBar {
NavigationBar { tabs.fastForEach {
tabs.fastForEach { NavigationBarItem(it)
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()
}
},
)
} }
} }
} }

View File

@ -1,7 +1,14 @@
package eu.kanade.tachiyomi.ui.setting 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.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
@ -55,7 +62,11 @@ class SettingsScreen private constructor(
SettingsGeneralScreen SettingsGeneralScreen
}, },
) { ) {
val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)
TwoPanelBox( TwoPanelBox(
modifier = Modifier
.windowInsetsPadding(insets)
.consumeWindowInsets(insets),
startContent = { startContent = {
CompositionLocalProvider(LocalBackPress provides parentNavigator::pop) { CompositionLocalProvider(LocalBackPress provides parentNavigator::pop) {
SettingsMainScreen.Content(twoPane = true) SettingsMainScreen.Content(twoPane = true)