From 20bd7bfaf8dc88709db93e3620bdcf64db192f5c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 16 Aug 2026 10:40:05 -0400 Subject: [PATCH] =?UTF-8?q?fix(android):=20let=20list=20content=20reach=20?= =?UTF-8?q?the=20MiniPlayer=20=E2=80=94=20#2681?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shell is a Column (content weight(1f), then the bar), so the content viewport already ends at the MiniPlayer's top edge. But nothing owned the bottom navigation-bar inset under edge-to-edge: each in-shell screen's own Scaffold claimed it via the default contentWindowInsets and padded its content up by the nav-bar height a second time. That padding is the dead strip the operator sees between the last list row and the bar — and the bar's own bottom was drawing under the gesture pill. ShellScaffold now owns the inset end to end: the content region consumes it, and a Spacer below the MiniPlayer re-holds the space for the system bar (unconditional — MiniPlayer renders nothing when no track is loaded). Modifier.consumeWindowInsets alone can't fix it: ScaffoldLayout reads contentWindowInsets.asPaddingValues() directly, outside the modifier consumption chain, so every in-shell Scaffold is handed the new zero ShellContentWindowInsets. The full-screen routes (NowPlaying / Queue / Login / ServerUrl) keep the default — no shell sits above them. Also drops the hardcoded 140dp bottom contentPadding on Album and Playlist detail, a Flutter-era value for a player bar that overlaid its list; here the shell reserves that space in layout already. --- .../minstrel/admin/ui/AdminLandingScreen.kt | 2 + .../admin/ui/AdminQuarantineScreen.kt | 2 + .../minstrel/admin/ui/AdminRequestsScreen.kt | 2 + .../admin/ui/AdminTagSourcesScreen.kt | 2 + .../minstrel/admin/ui/AdminUsersScreen.kt | 2 + .../minstrel/discover/ui/DiscoverScreen.kt | 2 + .../minstrel/home/ui/HomeScreen.kt | 2 + .../minstrel/library/ui/AlbumDetailScreen.kt | 4 +- .../minstrel/library/ui/ArtistDetailScreen.kt | 2 + .../minstrel/library/ui/LibraryScreen.kt | 2 + .../playlists/ui/PlaylistDetailScreen.kt | 4 +- .../playlists/ui/PlaylistsListScreen.kt | 2 + .../minstrel/requests/ui/RequestsScreen.kt | 2 + .../minstrel/search/ui/SearchScreen.kt | 2 + .../minstrel/settings/ui/SettingsScreen.kt | 2 + .../minstrel/shared/widgets/ShellScaffold.kt | 46 ++++++++++++++++++- 16 files changed, 75 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt index 0a60d78b..a3c0b9cf 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminLandingScreen.kt @@ -41,6 +41,7 @@ import com.fabledsword.minstrel.nav.AdminQuarantine import com.fabledsword.minstrel.nav.AdminRequests import com.fabledsword.minstrel.nav.AdminTagSources import com.fabledsword.minstrel.nav.AdminUsers +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.LoadingCentered import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar @@ -112,6 +113,7 @@ fun AdminLandingScreen( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineScreen.kt index 5822ad19..4031a5fc 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminQuarantineScreen.kt @@ -28,6 +28,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.fabledsword.minstrel.models.AdminQuarantineItemRef import com.fabledsword.minstrel.nav.AdminQuarantine +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered @@ -42,6 +43,7 @@ fun AdminQuarantineScreen( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsScreen.kt index bad71271..114102df 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminRequestsScreen.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.fabledsword.minstrel.models.RequestRef import com.fabledsword.minstrel.nav.AdminRequests +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered @@ -41,6 +42,7 @@ fun AdminRequestsScreen( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminTagSourcesScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminTagSourcesScreen.kt index cc6f0cf8..541130a7 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminTagSourcesScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminTagSourcesScreen.kt @@ -35,6 +35,7 @@ import androidx.navigation.NavHostController import com.fabledsword.minstrel.models.AdminTagSourceRef import com.fabledsword.minstrel.models.TagSourceTestResult import com.fabledsword.minstrel.nav.AdminTagSources +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered @@ -49,6 +50,7 @@ fun AdminTagSourcesScreen( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt index 1d4d5fff..2b232a27 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/admin/ui/AdminUsersScreen.kt @@ -49,6 +49,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import com.fabledsword.minstrel.models.AdminUserRef import com.fabledsword.minstrel.nav.AdminUsers +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold import kotlinx.coroutines.launch @@ -127,6 +128,7 @@ private fun AdminUsersScaffold( onRevokeInvite: (String) -> Unit, ) { Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt index 02a21224..f62d06a6 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/discover/ui/DiscoverScreen.kt @@ -42,6 +42,7 @@ import com.fabledsword.minstrel.models.LidarrRequestKind import com.fabledsword.minstrel.models.LidarrSearchResultRef import com.fabledsword.minstrel.models.SuggestionSnoozeRef import com.fabledsword.minstrel.nav.Discover +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar @@ -61,6 +62,7 @@ fun DiscoverScreen( val scope = rememberCoroutineScope() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt index c5c22095..83d66e9f 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/home/ui/HomeScreen.kt @@ -91,6 +91,7 @@ import com.fabledsword.minstrel.shared.VeilOutcome import com.fabledsword.minstrel.shared.VeilSessionResult import com.fabledsword.minstrel.shared.VeilSettleState import com.fabledsword.minstrel.shared.asCacheFirstStateFlow +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.ArtSettleTracker import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry @@ -564,6 +565,7 @@ fun HomeScreen( viewModel.transientMessages.collect { snackbarHostState.showSnackbar(it) } } Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt index ecb398d0..ee428610 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/AlbumDetailScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -52,6 +51,7 @@ import com.fabledsword.minstrel.models.TrackRef import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.shared.formatDuration +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.TrackRow import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LikeButton @@ -70,6 +70,7 @@ fun AlbumDetailScreen( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( @@ -165,7 +166,6 @@ private fun AlbumBody( ) { LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(bottom = 140.dp), ) { item { AlbumHeader( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailScreen.kt index f1fa4359..398b4e90 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/ArtistDetailScreen.kt @@ -56,6 +56,7 @@ import com.fabledsword.minstrel.models.albumCoverPath import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.shared.formatDuration +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.HorizontalScrollRow import com.fabledsword.minstrel.shared.widgets.LikeButton @@ -79,6 +80,7 @@ fun ArtistDetailScreen( } } Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt index ee290b44..1520d394 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/library/ui/LibraryScreen.kt @@ -43,6 +43,7 @@ import com.fabledsword.minstrel.nav.Library import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Shuffle import com.fabledsword.minstrel.shared.UiState +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar @@ -77,6 +78,7 @@ fun LibraryScreen( val scope = rememberCoroutineScope() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { Column { diff --git a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt index a63bed9a..4e69ffd6 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistDetailScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -75,6 +74,7 @@ import com.fabledsword.minstrel.playlists.data.PlaylistDetailRef import com.fabledsword.minstrel.playlists.data.PlaylistsRepository import com.fabledsword.minstrel.playlists.data.toPlayableTrackRefs import com.fabledsword.minstrel.shared.formatDuration +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.TrackRow import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LikeButton @@ -339,6 +339,7 @@ fun PlaylistDetailScreen( } } Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { @@ -430,7 +431,6 @@ private fun PlaylistDetailBody( ) { LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(bottom = 140.dp), ) { item { PlaylistHeader(detail.playlist, onPlayAll, onShuffleAll, onRegenerate) } item { HorizontalDivider() } diff --git a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt index f038550e..3df31460 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/playlists/ui/PlaylistsListScreen.kt @@ -41,6 +41,7 @@ import com.fabledsword.minstrel.playlists.data.playPlaylistShuffled import com.fabledsword.minstrel.shared.UiState import com.fabledsword.minstrel.shared.asCacheFirstStateFlow import com.fabledsword.minstrel.playlists.widgets.PlaylistCard +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered @@ -149,6 +150,7 @@ fun PlaylistsListScreen( viewModel.transientMessages.collect { snackbar.showSnackbar(it) } } Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt index 2856a704..3a55e608 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/requests/ui/RequestsScreen.kt @@ -45,6 +45,7 @@ import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.shared.UiState import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.nav.Requests +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.EmptyState import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered @@ -58,6 +59,7 @@ fun RequestsScreen( ) { val state by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt index 2766ba43..55abfcc9 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/search/ui/SearchScreen.kt @@ -55,6 +55,7 @@ import com.fabledsword.minstrel.models.TrackRef import com.fabledsword.minstrel.nav.AlbumDetail import com.fabledsword.minstrel.nav.ArtistDetail import com.fabledsword.minstrel.nav.Search as SearchRoute +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.TrackRow import com.fabledsword.minstrel.shared.widgets.ErrorRetry import com.fabledsword.minstrel.shared.widgets.LoadingCentered @@ -76,6 +77,7 @@ fun SearchScreen( LaunchedEffect(Unit) { focusRequester.requestFocus() } Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { TopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt index 1dfc17c6..52fa6333 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/settings/ui/SettingsScreen.kt @@ -55,6 +55,7 @@ import com.fabledsword.minstrel.nav.Admin import com.fabledsword.minstrel.nav.Requests import com.fabledsword.minstrel.nav.Settings as SettingsRoute import com.fabledsword.minstrel.nav.ServerUrl +import com.fabledsword.minstrel.shared.widgets.ShellContentWindowInsets import com.fabledsword.minstrel.shared.widgets.MinstrelTopAppBar import com.fabledsword.minstrel.theme.ThemeMode import com.fabledsword.minstrel.theme.ThemePreferenceViewModel @@ -81,6 +82,7 @@ fun SettingsScreen( } Scaffold( + contentWindowInsets = ShellContentWindowInsets, modifier = Modifier.fillMaxSize(), topBar = { MinstrelTopAppBar( diff --git a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt index d5e3310d..5e8b6881 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/shared/widgets/ShellScaffold.kt @@ -2,9 +2,14 @@ package com.fabledsword.minstrel.shared.widgets import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -20,6 +25,26 @@ import com.fabledsword.minstrel.update.ui.UpdateBanner import com.fabledsword.minstrel.update.ui.VersionTooOldBanner import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel +/** + * `contentWindowInsets` for a screen's own Scaffold when that screen + * renders inside [ShellScaffold]. + * + * The shell owns the window insets for in-shell routes: it consumes the + * status bar at the top and holds the navigation-bar space at the + * bottom, below the MiniPlayer. A screen Scaffold's default + * `contentWindowInsets` is the raw system bars, and Scaffold reads that + * value directly (`contentWindowInsets.asPaddingValues()`) rather than + * through the consumption-aware modifier chain — so the shell consuming + * the inset does NOT reach it. Left at the default, every in-shell + * screen pads its content up by the nav-bar height a second time: the + * dead strip between the last list row and the MiniPlayer. + * + * Full-screen routes (NowPlaying / Queue / Login / ServerUrl) keep the + * default — no shell sits above them, so their Scaffold is the only + * thing that can inset them. + */ +val ShellContentWindowInsets: WindowInsets = WindowInsets(0, 0, 0, 0) + /** * Outer shell wrapper for "in-app" routes — banners on top * (conditional, none implemented yet), routed screen filling the @@ -82,12 +107,31 @@ fun ShellScaffold( VersionTooOldBanner() UpdateBanner() ConnectionErrorBanner() - Box(modifier = Modifier.fillMaxWidth().weight(1f)) { + // The window is edge-to-edge, so something has to own the + // bottom navigation-bar inset, and the shell claims it once + // here: the content region consumes it so nothing inside a + // screen pads for it a second time (see + // [ShellContentWindowInsets] for the Scaffold half of that — + // Scaffold reads its insets outside the consumption chain), and + // the Spacer below re-holds the space for the system bar. + // Un-owned, the inset re-appears inside each screen as a dead + // strip between the last list row and the MiniPlayer. + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .consumeWindowInsets(WindowInsets.navigationBars), + ) { content() } SnackbarHost(hostState = snackbarHostState) MiniPlayer( onExpandClick = onExpandPlayer, ) + // Unconditional: the MiniPlayer renders nothing when no track + // is loaded, and the content region has already given the inset + // up, so this is what keeps a fresh install's last row off the + // gesture pill. + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) } }