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
@ -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 }
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user