diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index 347f1d16..44239750 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -14,6 +14,7 @@ import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/image_cache_utils.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart'; import 'package:spotiflac_android/widgets/download_service_picker.dart'; import 'package:spotiflac_android/widgets/animation_utils.dart'; @@ -336,6 +337,7 @@ class _AlbumScreenState extends ConsumerState { final colorScheme = Theme.of(context).colorScheme; final tracks = _tracks ?? []; final pageBackgroundColor = colorScheme.surface; + final bottomInset = context.navBarBottomInset; return Scaffold( backgroundColor: pageBackgroundColor, @@ -361,7 +363,7 @@ class _AlbumScreenState extends ConsumerState { if (!_isLoading && _error == null && tracks.isNotEmpty) ...[ _buildTrackList(context, colorScheme, tracks), ], - const SliverToBoxAdapter(child: SizedBox(height: 32)), + SliverToBoxAdapter(child: SizedBox(height: 32 + bottomInset)), ], ), ); diff --git a/lib/screens/artist_screen.dart b/lib/screens/artist_screen.dart index 032ccda3..4a6ab9a4 100644 --- a/lib/screens/artist_screen.dart +++ b/lib/screens/artist_screen.dart @@ -15,6 +15,7 @@ import 'package:spotiflac_android/providers/playback_provider.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/screens/album_screen.dart'; import 'package:spotiflac_android/screens/home_tab.dart' show ExtensionAlbumScreen; @@ -456,6 +457,7 @@ class _ArtistScreenState extends ConsumerState { final albumsOnly = _albumsOnlyBucket; final singles = _singlesBucket; final compilations = _compilationsBucket; + final bottomInset = context.navBarBottomInset; final hasDiscography = !_isLoadingDiscography && _error == null && albums.isNotEmpty; @@ -542,6 +544,7 @@ class _ArtistScreenState extends ConsumerState { SliverToBoxAdapter( child: SizedBox(height: _isSelectionMode ? 120 : 32), ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), if (_isSelectionMode) diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index 0a9fcd0e..616bb48b 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -15,6 +15,7 @@ import 'package:spotiflac_android/utils/audio_conversion_utils.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/image_cache_utils.dart'; import 'package:spotiflac_android/utils/lyrics_metadata_helper.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/providers/download_queue_provider.dart'; import 'package:spotiflac_android/widgets/batch_progress_dialog.dart'; import 'package:spotiflac_android/widgets/batch_convert_sheet.dart'; @@ -358,6 +359,7 @@ class _DownloadedAlbumScreenState extends ConsumerState { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final bottomPadding = MediaQuery.of(context).padding.bottom; + final bottomInset = context.navBarBottomInset; final tracksValue = ref.watch( downloadedAlbumTracksProvider( @@ -413,6 +415,7 @@ class _DownloadedAlbumScreenState extends ConsumerState { SliverToBoxAdapter( child: SizedBox(height: _isSelectionMode ? 120 : 32), ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), diff --git a/lib/screens/favorite_artists_screen.dart b/lib/screens/favorite_artists_screen.dart index 63f3c5ec..d30e196c 100644 --- a/lib/screens/favorite_artists_screen.dart +++ b/lib/screens/favorite_artists_screen.dart @@ -6,6 +6,7 @@ import 'package:spotiflac_android/providers/library_collections_provider.dart'; import 'package:spotiflac_android/screens/artist_screen.dart'; import 'package:spotiflac_android/services/cover_cache_manager.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/widgets/animation_utils.dart'; class FavoriteArtistsScreen extends ConsumerWidget { @@ -18,6 +19,7 @@ class FavoriteArtistsScreen extends ConsumerWidget { ); final colorScheme = Theme.of(context).colorScheme; final topPadding = normalizedHeaderTopPadding(context); + final bottomInset = context.navBarBottomInset; return Scaffold( body: CustomScrollView( @@ -155,6 +157,7 @@ class FavoriteArtistsScreen extends ConsumerWidget { ); }, ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), ); diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 9976d2d5..348168cd 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -21,6 +21,7 @@ import 'package:spotiflac_android/services/csv_import_service.dart'; import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; import 'package:spotiflac_android/screens/playlist_screen.dart'; @@ -1205,6 +1206,7 @@ class _HomeTabState extends ConsumerState final mediaQuery = MediaQuery.of(context); final screenHeight = mediaQuery.size.height; final topPadding = normalizedHeaderTopPadding(context); + final bottomInset = context.navBarBottomInset; final hasHistoryItems = ref.watch( _homeHistoryPreviewProvider.select((items) => items.isNotEmpty), ); @@ -1540,6 +1542,7 @@ class _HomeTabState extends ConsumerState ); }, ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), ), diff --git a/lib/screens/library_playlists_screen.dart b/lib/screens/library_playlists_screen.dart index 5c119857..cf0a0046 100644 --- a/lib/screens/library_playlists_screen.dart +++ b/lib/screens/library_playlists_screen.dart @@ -10,6 +10,7 @@ import 'package:spotiflac_android/screens/library_tracks_folder_screen.dart'; import 'package:spotiflac_android/services/cover_cache_manager.dart'; import 'package:spotiflac_android/widgets/bottom_sheet_option_tile.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; class LibraryPlaylistsScreen extends ConsumerWidget { const LibraryPlaylistsScreen({super.key}); @@ -21,6 +22,7 @@ class LibraryPlaylistsScreen extends ConsumerWidget { ); final colorScheme = Theme.of(context).colorScheme; final topPadding = normalizedHeaderTopPadding(context); + final bottomInset = context.navBarBottomInset; return Scaffold( body: CustomScrollView( @@ -132,6 +134,7 @@ class LibraryPlaylistsScreen extends ConsumerWidget { ); }, childCount: playlists.length * 2 - 1), ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), floatingActionButton: FloatingActionButton.extended( diff --git a/lib/screens/library_tracks_folder_screen.dart b/lib/screens/library_tracks_folder_screen.dart index 3c2009d6..47acea8a 100644 --- a/lib/screens/library_tracks_folder_screen.dart +++ b/lib/screens/library_tracks_folder_screen.dart @@ -14,6 +14,7 @@ import 'package:spotiflac_android/providers/playback_provider.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/services/cover_cache_manager.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/screens/track_metadata_screen.dart'; import 'package:spotiflac_android/widgets/download_service_picker.dart'; import 'package:spotiflac_android/widgets/playlist_picker_sheet.dart'; @@ -322,6 +323,7 @@ class _LibraryTracksFolderScreenState .maybeWhen(data: (keys) => keys, orElse: () => const {}); final bottomPadding = MediaQuery.of(context).padding.bottom; + final bottomInset = context.navBarBottomInset; return PopScope( canPop: !_isSelectionMode, @@ -379,6 +381,7 @@ class _LibraryTracksFolderScreenState SliverToBoxAdapter( child: SizedBox(height: _isSelectionMode ? 200 : 32), ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), diff --git a/lib/screens/local_album_screen.dart b/lib/screens/local_album_screen.dart index 1cedc93d..92762641 100644 --- a/lib/screens/local_album_screen.dart +++ b/lib/screens/local_album_screen.dart @@ -12,6 +12,7 @@ import 'package:spotiflac_android/utils/audio_conversion_utils.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/image_cache_utils.dart'; import 'package:spotiflac_android/utils/lyrics_metadata_helper.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/services/library_database.dart'; import 'package:spotiflac_android/services/ffmpeg_service.dart'; import 'package:spotiflac_android/services/replaygain_service.dart'; @@ -252,6 +253,7 @@ class _LocalAlbumScreenState extends ConsumerState { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final bottomPadding = MediaQuery.of(context).padding.bottom; + final bottomInset = context.navBarBottomInset; final tracks = _sortedTracksCache; if (tracks.isEmpty) { @@ -288,6 +290,7 @@ class _LocalAlbumScreenState extends ConsumerState { SliverToBoxAdapter( child: SizedBox(height: _isSelectionMode ? 120 : 32), ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index b9f6c7da..377a66a8 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -9,6 +9,7 @@ import 'package:spotiflac_android/providers/library_collections_provider.dart'; import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/image_cache_utils.dart'; import 'package:spotiflac_android/utils/string_utils.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/local_library_provider.dart'; import 'package:spotiflac_android/providers/playback_provider.dart'; @@ -253,7 +254,9 @@ class _PlaylistScreenState extends ConsumerState { _buildAppBar(context, colorScheme), _buildInfoCard(context, colorScheme), _buildTrackList(context, colorScheme), - const SliverToBoxAdapter(child: SizedBox(height: 32)), + SliverToBoxAdapter( + child: SizedBox(height: 32 + context.navBarBottomInset), + ), ], ), ); diff --git a/lib/screens/repo_tab.dart b/lib/screens/repo_tab.dart index fd97f0d0..8fef33b4 100644 --- a/lib/screens/repo_tab.dart +++ b/lib/screens/repo_tab.dart @@ -7,6 +7,7 @@ import 'package:spotiflac_android/widgets/settings_group.dart'; import 'package:spotiflac_android/widgets/animation_utils.dart'; import 'package:spotiflac_android/screens/store/extension_details_screen.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; class RepoTab extends ConsumerStatefulWidget { const RepoTab({super.key}); @@ -76,6 +77,7 @@ class _RepoTabState extends ConsumerState { } final colorScheme = Theme.of(context).colorScheme; final topPadding = normalizedHeaderTopPadding(context); + final bottomInset = context.navBarBottomInset; return Scaffold( body: RefreshIndicator( @@ -311,6 +313,7 @@ class _RepoTabState extends ConsumerState { const SliverToBoxAdapter(child: SizedBox(height: 16)), ], ], + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), ], ), ), diff --git a/lib/screens/settings/settings_tab.dart b/lib/screens/settings/settings_tab.dart index bccd5b60..25df703c 100644 --- a/lib/screens/settings/settings_tab.dart +++ b/lib/screens/settings/settings_tab.dart @@ -15,6 +15,7 @@ import 'package:spotiflac_android/screens/settings/cache_management_page.dart'; import 'package:spotiflac_android/screens/settings/donate_page.dart'; import 'package:spotiflac_android/screens/settings/log_screen.dart'; import 'package:spotiflac_android/utils/app_bar_layout.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; import 'package:spotiflac_android/widgets/animation_utils.dart'; @@ -25,6 +26,7 @@ class SettingsTab extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; final topPadding = normalizedHeaderTopPadding(context); + final bottomInset = context.navBarBottomInset; return CustomScrollView( slivers: [ @@ -183,6 +185,7 @@ class SettingsTab extends ConsumerWidget { ), ), + SliverToBoxAdapter(child: SizedBox(height: bottomInset)), const SliverFillRemaining(hasScrollBody: false, child: SizedBox()), ], ); diff --git a/lib/screens/store/extension_details_screen.dart b/lib/screens/store/extension_details_screen.dart index 65629665..0b9ab038 100644 --- a/lib/screens/store/extension_details_screen.dart +++ b/lib/screens/store/extension_details_screen.dart @@ -4,6 +4,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/providers/store_provider.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; +import 'package:spotiflac_android/utils/nav_bar_inset.dart'; class ExtensionDetailsScreen extends ConsumerStatefulWidget { final StoreExtension extension; @@ -69,7 +70,9 @@ class _ExtensionDetailsScreenState ), _buildCapabilities(context, liveExtension, colorScheme), - const SliverToBoxAdapter(child: SizedBox(height: 32)), + SliverToBoxAdapter( + child: SizedBox(height: 32 + context.navBarBottomInset), + ), ], ), ); diff --git a/lib/utils/nav_bar_inset.dart b/lib/utils/nav_bar_inset.dart new file mode 100644 index 00000000..0ca43da5 --- /dev/null +++ b/lib/utils/nav_bar_inset.dart @@ -0,0 +1,12 @@ +import 'package:flutter/widgets.dart'; + +/// Bottom inset needed to clear the transparent shell navigation bar. +/// +/// The shell Scaffold uses `extendBody: true`, so its body (and any route +/// pushed inside the tab navigators) receives the navbar height plus the system +/// gesture inset as `MediaQuery.padding.bottom`. Scrollable screens add this as +/// trailing padding so their last item can scroll clear of the bar while the +/// content still shows faintly behind it. +extension NavBarInset on BuildContext { + double get navBarBottomInset => MediaQuery.paddingOf(this).bottom; +}