fix: use layout constraints for grid column count

This commit is contained in:
edde746
2026-02-04 09:06:05 +01:00
parent 731102357d
commit 96c6462d13
6 changed files with 80 additions and 74 deletions
+26 -18
View File
@@ -159,27 +159,35 @@ class _CollectionDetailScreenState extends BaseMediaListDetailScreen<CollectionD
Widget _buildFocusableGrid() {
return Consumer<SettingsProvider>(
builder: (context, settingsProvider, child) {
final columnCount = GridSizeCalculator.getColumnCount(context, settingsProvider.libraryDensity);
final maxExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, settingsProvider.libraryDensity);
return SliverPadding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
sliver: SliverGrid.builder(
gridDelegate: MediaGridDelegate.createDelegate(context: context, density: settingsProvider.libraryDensity),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final inFirstRow = GridSizeCalculator.isFirstRow(index, columnCount);
final focusNode = index == 0 ? firstItemFocusNode : getGridItemFocusNode(index);
sliver: SliverLayoutBuilder(
builder: (context, constraints) {
final columnCount = GridSizeCalculator.getColumnCount(constraints.crossAxisExtent, maxExtent);
return SliverGrid.builder(
gridDelegate: MediaGridDelegate.createDelegate(
context: context,
density: settingsProvider.libraryDensity,
),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final inFirstRow = GridSizeCalculator.isFirstRow(index, columnCount);
final focusNode = index == 0 ? firstItemFocusNode : getGridItemFocusNode(index);
return FocusableMediaCard(
key: Key(item.ratingKey),
item: item,
focusNode: focusNode,
onRefresh: updateItem,
collectionId: widget.collection.ratingKey,
onListRefresh: loadItems,
onNavigateUp: inFirstRow ? navigateToAppBar : null,
onBack: handleBackFromContent,
onFocusChange: (hasFocus) => trackGridItemFocus(index, hasFocus),
return FocusableMediaCard(
key: Key(item.ratingKey),
item: item,
focusNode: focusNode,
onRefresh: updateItem,
collectionId: widget.collection.ratingKey,
onListRefresh: loadItems,
onNavigateUp: inFirstRow ? navigateToAppBar : null,
onBack: handleBackFromContent,
onFocusChange: (hasFocus) => trackGridItemFocus(index, hasFocus),
);
},
);
},
),
+1 -8
View File
@@ -7,7 +7,6 @@ import '../../providers/multi_server_provider.dart';
import '../../providers/settings_provider.dart';
import '../../services/gamepad_service.dart';
import '../../utils/grid_size_calculator.dart';
import '../../utils/layout_constants.dart';
import '../../utils/platform_detector.dart';
import '../../widgets/desktop_app_bar.dart';
import '../../widgets/focusable_tab_chip.dart';
@@ -328,11 +327,6 @@ class _DownloadsGridContentState extends State<_DownloadsGridContent> {
MainScreenFocusScope.of(context)?.focusSidebar();
}
/// Calculate column count based on actual available width.
int _calculateColumnCount(double availableWidth, double maxCrossAxisExtent, double crossAxisSpacing) {
return ((availableWidth + crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil().clamp(1, 100);
}
@override
Widget build(BuildContext context) {
return Consumer2<DownloadProvider, SettingsProvider>(
@@ -348,13 +342,12 @@ class _DownloadsGridContentState extends State<_DownloadsGridContent> {
// Extra top padding for focus decoration (scale + border extends beyond item bounds)
const effectivePadding = EdgeInsets.only(left: 8, right: 8, top: 8);
final maxCrossAxisExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, settingsProvider.libraryDensity);
const crossAxisSpacing = GridLayoutConstants.crossAxisSpacing;
// Use LayoutBuilder to get actual available width (accounting for sidebar)
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth - effectivePadding.left - effectivePadding.right;
final columnCount = _calculateColumnCount(availableWidth, maxCrossAxisExtent, crossAxisSpacing);
final columnCount = GridSizeCalculator.getColumnCount(availableWidth, maxCrossAxisExtent);
return GridView.builder(
padding: effectivePadding,
@@ -80,13 +80,6 @@ class AdaptiveMediaGrid<T> extends StatelessWidget {
MainScreenFocusScope.of(context)?.focusSidebar();
}
/// Calculate column count based on actual available width.
/// Uses the same formula as Flutter's SliverGridDelegateWithMaxCrossAxisExtent.
int _calculateColumnCount(double availableWidth, double maxCrossAxisExtent) {
final crossAxisSpacing = GridLayoutConstants.crossAxisSpacing;
return ((availableWidth + crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil().clamp(1, 100);
}
/// Builds either a list or grid view based on the view mode
Widget _buildItemsView(BuildContext context, ViewMode viewMode, LibraryDensity density) {
final basePadding = padding ?? GridLayoutConstants.gridPadding;
@@ -120,7 +113,7 @@ class AdaptiveMediaGrid<T> extends StatelessWidget {
return LayoutBuilder(
builder: (context, constraints) {
final availableWidth = constraints.maxWidth - horizontalPadding;
final columnCount = _calculateColumnCount(availableWidth, maxCrossAxisExtent);
final columnCount = GridSizeCalculator.getColumnCount(availableWidth, maxCrossAxisExtent);
return GridView.builder(
padding: effectivePadding,
@@ -723,24 +723,29 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<PlexMetadata, LibraryBr
);
} else {
// In grid view, calculate columns and pass to item builder
final columnCount = GridSizeCalculator.getColumnCount(context, settingsProvider.libraryDensity);
// Use 16:9 aspect ratio when browsing episodes with episode thumbnail mode
final useWideRatio =
_selectedGrouping == 'episodes' && settingsProvider.episodePosterMode == EpisodePosterMode.episodeThumbnail;
final maxExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, settingsProvider.libraryDensity);
return SliverPadding(
padding: const EdgeInsets.fromLTRB(8, _gridTopPadding, 8, 8),
sliver: SliverGrid.builder(
gridDelegate: MediaGridDelegate.createDelegate(
context: context,
density: settingsProvider.libraryDensity,
useWideAspectRatio: useWideRatio,
),
itemCount: itemCount,
itemBuilder: (context, index) => _buildMediaCardItem(
index,
isFirstRow: GridSizeCalculator.isFirstRow(index, columnCount),
isFirstColumn: GridSizeCalculator.isFirstColumn(index, columnCount),
),
sliver: SliverLayoutBuilder(
builder: (context, constraints) {
final columnCount = GridSizeCalculator.getColumnCount(constraints.crossAxisExtent, maxExtent);
return SliverGrid.builder(
gridDelegate: MediaGridDelegate.createDelegate(
context: context,
density: settingsProvider.libraryDensity,
useWideAspectRatio: useWideRatio,
),
itemCount: itemCount,
itemBuilder: (context, index) => _buildMediaCardItem(
index,
isFirstRow: GridSizeCalculator.isFirstRow(index, columnCount),
isFirstColumn: GridSizeCalculator.isFirstColumn(index, columnCount),
),
);
},
),
);
}
@@ -784,29 +784,37 @@ class _PlaylistDetailScreenState extends BaseMediaListDetailScreen<PlaylistDetai
Widget _buildSmartPlaylistGrid(bool isKeyboardMode) {
return Consumer<SettingsProvider>(
builder: (context, settingsProvider, child) {
final columnCount = GridSizeCalculator.getColumnCount(context, settingsProvider.libraryDensity);
final maxExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, settingsProvider.libraryDensity);
return SliverPadding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
sliver: SliverGrid.builder(
gridDelegate: MediaGridDelegate.createDelegate(context: context, density: settingsProvider.libraryDensity),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final isFirstRow = GridSizeCalculator.isFirstRow(index, columnCount);
final focusNode = index == 0 ? _firstItemFocusNode : _getGridItemFocusNode(index);
sliver: SliverLayoutBuilder(
builder: (context, constraints) {
final columnCount = GridSizeCalculator.getColumnCount(constraints.crossAxisExtent, maxExtent);
return SliverGrid.builder(
gridDelegate: MediaGridDelegate.createDelegate(
context: context,
density: settingsProvider.libraryDensity,
),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final isFirstRow = GridSizeCalculator.isFirstRow(index, columnCount);
final focusNode = index == 0 ? _firstItemFocusNode : _getGridItemFocusNode(index);
return FocusableMediaCard(
key: Key(item.ratingKey),
item: item,
focusNode: focusNode,
onRefresh: updateItem,
onNavigateUp: isFirstRow ? _navigateToAppBar : null,
onBack: _handleBackFromContent,
onFocusChange: (hasFocus) {
if (hasFocus) {
_lastFocusedGridIndex = index;
_lastFocusedContentVersion = _contentVersion;
}
return FocusableMediaCard(
key: Key(item.ratingKey),
item: item,
focusNode: focusNode,
onRefresh: updateItem,
onNavigateUp: isFirstRow ? _navigateToAppBar : null,
onBack: _handleBackFromContent,
onFocusChange: (hasFocus) {
if (hasFocus) {
_lastFocusedGridIndex = index;
_lastFocusedContentVersion = _contentVersion;
}
},
);
},
);
},
+6 -7
View File
@@ -122,18 +122,17 @@ class GridSizeCalculator {
return MediaQuery.of(context).size.width <= tabletBreakpoint;
}
/// Calculates the number of columns in a grid based on screen width and density.
/// Calculates the number of columns for a given available width.
///
/// Uses the same formula as Flutter's SliverGridDelegateWithMaxCrossAxisExtent:
/// `((crossAxisExtent + crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil()`
///
/// [horizontalPadding] should match the grid's total horizontal padding.
static int getColumnCount(BuildContext context, LibraryDensity density, {double horizontalPadding = 16}) {
final screenWidth = MediaQuery.of(context).size.width - horizontalPadding;
final maxCrossAxisExtent = getMaxCrossAxisExtent(context, density);
/// [crossAxisExtent] should come from layout constraints (e.g. `SliverLayoutBuilder`
/// or `LayoutBuilder`), not from `MediaQuery`, to account for sidebars or other
/// elements that reduce the grid's actual width.
static int getColumnCount(double crossAxisExtent, double maxCrossAxisExtent) {
final crossAxisSpacing = GridLayoutConstants.crossAxisSpacing;
// Match Flutter's grid delegate calculation
return ((screenWidth + crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil().clamp(1, 100);
return ((crossAxisExtent + crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil().clamp(1, 100);
}
/// Check if the given index is in the first row of a grid with given column count.