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:
parent
820ed6a468
commit
ca500da4d8
4 changed files with 88 additions and 53 deletions
|
@ -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 a new issue