refactor: extract MainScaffold for better separation of navigation chrome logic

This commit is contained in:
2026-07-08 15:34:24 +02:00
parent 7d8ba628e0
commit 2399a440dd
2 changed files with 72 additions and 45 deletions
@@ -1,8 +1,6 @@
package bzh.ajaury.chombev package bzh.ajaury.chombev
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.saveable.rememberSerializable import androidx.compose.runtime.saveable.rememberSerializable
@@ -13,8 +11,7 @@ import androidx.savedstate.compose.serialization.serializers.SnapshotStateListSe
import bzh.ajaury.chombev.core.coroutines.di.coroutinesModule import bzh.ajaury.chombev.core.coroutines.di.coroutinesModule
import bzh.ajaury.chombev.core.logging.di.loggingModule import bzh.ajaury.chombev.core.logging.di.loggingModule
import bzh.ajaury.chombev.di.sharedModule import bzh.ajaury.chombev.di.sharedModule
import bzh.ajaury.chombev.navigation.MainBottomBar import bzh.ajaury.chombev.navigation.MainScaffold
import bzh.ajaury.chombev.navigation.MainTopBar
import bzh.ajaury.chombev.navigation.Route import bzh.ajaury.chombev.navigation.Route
import bzh.ajaury.chombev.navigation.WebPages import bzh.ajaury.chombev.navigation.WebPages
import bzh.ajaury.chombev.player.ui.PlayerScreen import bzh.ajaury.chombev.player.ui.PlayerScreen
@@ -49,38 +46,21 @@ fun App() {
val darkTheme = isSystemInDarkTheme() val darkTheme = isSystemInDarkTheme()
// The current top destination drives the shared chrome: the top and bottom bars are shown
// only on the main screens (Records and the web pages), not on the Player detail screen.
val currentRoute = backStack.lastOrNull()
val selectedPage = (currentRoute as? Route.Web)?.page
val isMainScreen = currentRoute is Route.Records || currentRoute is Route.Web
ChomBevTheme(darkTheme = darkTheme) { ChomBevTheme(darkTheme = darkTheme) {
Scaffold( // Each destination owns its chrome (see MainScaffold) so navigating to the full-screen
// Let each screen's bars/insets apply; keeps the Player's own scaffold from double-padding. // player animates as a whole scene without the main bars reflowing the outgoing screen.
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = {
if (isMainScreen) {
MainTopBar()
}
},
bottomBar = {
if (isMainScreen) {
MainBottomBar(
selectedPage = selectedPage,
onRecordsClick = { backStack.selectMainTab(null) },
onDeepenClick = { backStack.selectMainTab(WebPages.DEEPEN) },
onAboutClick = { backStack.selectMainTab(WebPages.ABOUT) },
)
}
},
) { innerPadding ->
NavDisplay( NavDisplay(
backStack = backStack, backStack = backStack,
onBack = { backStack.removeLastOrNull() }, onBack = { backStack.removeLastOrNull() },
entryProvider = { key -> entryProvider = { key ->
when (key) { when (key) {
is Route.Records -> NavEntry(key) { is Route.Records -> NavEntry(key) {
MainScaffold(
selectedPage = null,
onRecordsClick = { backStack.selectMainTab(null) },
onDeepenClick = { backStack.selectMainTab(WebPages.DEEPEN) },
onAboutClick = { backStack.selectMainTab(WebPages.ABOUT) },
) { innerPadding ->
RecordsScreen( RecordsScreen(
contentPadding = innerPadding, contentPadding = innerPadding,
onRecordClick = { record -> onRecordClick = { record ->
@@ -88,6 +68,7 @@ fun App() {
}, },
) )
} }
}
is Route.Player -> NavEntry(key) { is Route.Player -> NavEntry(key) {
PlayerScreen( PlayerScreen(
@@ -97,6 +78,12 @@ fun App() {
} }
is Route.Web -> NavEntry(key) { is Route.Web -> NavEntry(key) {
MainScaffold(
selectedPage = key.page,
onRecordsClick = { backStack.selectMainTab(null) },
onDeepenClick = { backStack.selectMainTab(WebPages.DEEPEN) },
onAboutClick = { backStack.selectMainTab(WebPages.ABOUT) },
) { innerPadding ->
WebViewScreen( WebViewScreen(
page = key.page, page = key.page,
contentPadding = innerPadding, contentPadding = innerPadding,
@@ -104,12 +91,12 @@ fun App() {
) )
} }
} }
}
}, },
) )
} }
} }
} }
}
// Selecting a main tab keeps Records as the root, so system Back always returns home. // Selecting a main tab keeps Records as the root, so system Back always returns home.
private fun MutableList<Route>.selectMainTab(page: WebPage?) { private fun MutableList<Route>.selectMainTab(page: WebPage?) {
@@ -0,0 +1,40 @@
package bzh.ajaury.chombev.navigation
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import bzh.ajaury.chombev.web.model.WebPage
/**
* Scaffold shared by the main destinations (Records, Deepen, About): the app [MainTopBar] and the
* [MainBottomBar].
*
* Each main destination wraps its own content in a [MainScaffold] rather than sitting inside one
* shared outer scaffold. This keeps the chrome part of the destination, so it animates with it and
* its content padding stays stable during navigation — avoiding the layout jump that happened when
* pushing a full-screen detail (the player) instantly removed the shared bars.
*/
@Composable
fun MainScaffold(
selectedPage: WebPage?,
onRecordsClick: () -> Unit = {},
onDeepenClick: () -> Unit = {},
onAboutClick: () -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
Scaffold(
// Let each bar apply its own insets; matches the player's standalone scaffold.
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = { MainTopBar() },
bottomBar = {
MainBottomBar(
selectedPage = selectedPage,
onRecordsClick = onRecordsClick,
onDeepenClick = onDeepenClick,
onAboutClick = onAboutClick,
)
},
content = content,
)
}