perf(grids): rebuild media grids on width change, not every scroll tick
This commit is contained in:
@@ -34,8 +34,10 @@ import '../library_alpha_scroll_metrics.dart';
|
||||
import '../library_filter_sort_loader.dart';
|
||||
import '../../../widgets/focusable_media_card.dart';
|
||||
import '../../../widgets/focusable_filter_chip.dart';
|
||||
import '../../../widgets/listenable_selector.dart';
|
||||
import '../../../widgets/loading_indicator_box.dart';
|
||||
import '../../../widgets/media_grid_delegate.dart';
|
||||
import '../../../widgets/sliver_cross_axis_layout_builder.dart';
|
||||
import '../../../widgets/media_card_list_layout.dart';
|
||||
import '../../../widgets/bottom_sheet_page_scaffold.dart';
|
||||
import '../../../widgets/overlay_sheet.dart';
|
||||
@@ -1311,26 +1313,31 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
top: overlayTopPadding,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
// Select the derived letter rather than listening to the raw
|
||||
// index: the index changes every scrolled row, but the bar only
|
||||
// needs a rebuild when the letter itself flips.
|
||||
child: _isPhone(context)
|
||||
? ValueListenableBuilder<int>(
|
||||
valueListenable: _currentFirstVisibleIndex,
|
||||
builder: (context, visibleIndex, _) => ValueListenableBuilder<bool>(
|
||||
? ListenableSelector<String>(
|
||||
listenable: _currentFirstVisibleIndex,
|
||||
selector: () => _alphaLetterFor(_currentFirstVisibleIndex.value),
|
||||
builder: (context, currentLetter, _) => ValueListenableBuilder<bool>(
|
||||
valueListenable: _isScrollActive,
|
||||
builder: (context, scrolling, _) => AlphaScrollHandle(
|
||||
firstCharacters: _firstCharacters,
|
||||
onJump: _jumpToIndex,
|
||||
currentLetter: _alphaLetterFor(visibleIndex),
|
||||
currentLetter: currentLetter,
|
||||
descending: _isTitleSortDescending,
|
||||
isScrolling: scrolling,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentFirstVisibleIndex,
|
||||
builder: (context, visibleIndex, _) => AlphaJumpBar(
|
||||
: ListenableSelector<String>(
|
||||
listenable: _currentFirstVisibleIndex,
|
||||
selector: () => _alphaLetterFor(_currentFirstVisibleIndex.value),
|
||||
builder: (context, currentLetter, _) => AlphaJumpBar(
|
||||
firstCharacters: _firstCharacters,
|
||||
onJump: _jumpToIndex,
|
||||
currentLetter: _alphaLetterFor(visibleIndex),
|
||||
currentLetter: currentLetter,
|
||||
descending: _isTitleSortDescending,
|
||||
focusNode: _alphaJumpBarFocusNode,
|
||||
onNavigateLeft: _navigateToGridNearScroll,
|
||||
@@ -1702,72 +1709,54 @@ class _LibraryBrowseTabState extends BaseLibraryTabState<MediaItem, LibraryBrows
|
||||
|
||||
if (viewMode == ViewMode.list) {
|
||||
// In list view, all items are in a single column (first column)
|
||||
_setListScrollMetrics(density: libraryDensity, usesWideAspectRatio: useWideRatio);
|
||||
return SliverPadding(
|
||||
padding: .fromLTRB(8, topPadding, rightPadding, 8),
|
||||
sliver: SliverLayoutBuilder(
|
||||
builder: (context, _) {
|
||||
_setListScrollMetrics(density: libraryDensity, usesWideAspectRatio: useWideRatio);
|
||||
return SliverList.builder(
|
||||
sliver: SliverList.builder(
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
final child = _buildMediaCardItem(
|
||||
index,
|
||||
isFirstRow: index == 0,
|
||||
isFirstColumn: true, // List view = single column
|
||||
isLastColumn: true,
|
||||
disableScale: true,
|
||||
columnCount: 1,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
final child = _buildMediaCardItem(
|
||||
index,
|
||||
isFirstRow: index == 0,
|
||||
isFirstColumn: true, // List view = single column
|
||||
isLastColumn: true,
|
||||
disableScale: true,
|
||||
columnCount: 1,
|
||||
itemCount: itemCount,
|
||||
);
|
||||
return index == 0 ? _buildMeasuredFirstListItem(child) : child;
|
||||
},
|
||||
);
|
||||
return index == 0 ? _buildMeasuredFirstListItem(child) : child;
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// In grid view, calculate columns and pass to item builder
|
||||
// Use 16:9 aspect ratio when browsing episodes with episode thumbnail mode
|
||||
final baseMaxExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, libraryDensity);
|
||||
final effectiveMaxExtent = useWideRatio ? baseMaxExtent * 1.8 : baseMaxExtent;
|
||||
final hasAlphaBarReservation = rightPadding > 8.0;
|
||||
return SliverPadding(
|
||||
padding: .fromLTRB(8, topPadding, rightPadding, 8),
|
||||
sliver: SliverLayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final gridSpacing = MediaGridDelegate.spacingFor(context: context, fullBleedImage: fullCardLayout);
|
||||
// Compute column count from the width the grid would have without the alpha
|
||||
// bar's reservation, so toggling the bar doesn't repack the grid into one
|
||||
// fewer column and blow up poster size.
|
||||
final baselineWidth = constraints.crossAxisExtent + (rightPadding - 8.0);
|
||||
final columnCount = GridSizeCalculator.getColumnCount(
|
||||
baselineWidth,
|
||||
effectiveMaxExtent,
|
||||
crossAxisSpacing: gridSpacing,
|
||||
sliver: SliverCrossAxisLayoutBuilder(
|
||||
builder: (context, crossAxisExtent) {
|
||||
final geometry = MediaGridGeometry.resolve(
|
||||
context: context,
|
||||
crossAxisExtent: crossAxisExtent,
|
||||
// Compute column count from the width the grid would have without
|
||||
// the alpha bar's reservation, so toggling the bar doesn't repack
|
||||
// the grid into one fewer column and blow up poster size.
|
||||
crossAxisExtentForColumnCount: hasAlphaBarReservation ? crossAxisExtent + (rightPadding - 8.0) : null,
|
||||
density: libraryDensity,
|
||||
useWideAspectRatio: useWideRatio,
|
||||
fullBleedImage: fullCardLayout,
|
||||
);
|
||||
final columnCount = geometry.columnCount;
|
||||
// Cache grid metrics for alpha jump bar scroll calculations
|
||||
final itemWidth = GridSizeCalculator.getCellWidthForColumnCount(
|
||||
constraints.crossAxisExtent,
|
||||
columnCount,
|
||||
crossAxisSpacing: gridSpacing,
|
||||
);
|
||||
final itemHeight =
|
||||
itemWidth /
|
||||
MediaGridDelegate.aspectRatioFor(useWideAspectRatio: useWideRatio, fullBleedImage: fullCardLayout);
|
||||
_scrollMetrics = LibraryAlphaScrollMetrics(
|
||||
columnCount: columnCount,
|
||||
rowHeight: itemHeight + gridSpacing,
|
||||
itemWidth: itemWidth,
|
||||
itemHeight: itemHeight,
|
||||
rowHeight: geometry.itemHeight + geometry.spacing,
|
||||
itemWidth: geometry.itemWidth,
|
||||
itemHeight: geometry.itemHeight,
|
||||
);
|
||||
return SliverGrid.builder(
|
||||
gridDelegate: MediaGridDelegate.createDelegate(
|
||||
context: context,
|
||||
density: libraryDensity,
|
||||
useWideAspectRatio: useWideRatio,
|
||||
fullBleedImage: fullCardLayout,
|
||||
maxCrossAxisExtentOverride: hasAlphaBarReservation ? itemWidth : null,
|
||||
),
|
||||
gridDelegate: geometry.delegate,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) => _buildMediaCardItem(
|
||||
index,
|
||||
|
||||
@@ -15,6 +15,7 @@ import '../../../widgets/focusable_media_card.dart';
|
||||
import '../../../widgets/media_grid_delegate.dart';
|
||||
import '../../../widgets/settings_builder.dart';
|
||||
import '../../../widgets/skeleton_media_card.dart';
|
||||
import '../../../widgets/sliver_cross_axis_layout_builder.dart';
|
||||
import '../../../i18n/strings.g.dart';
|
||||
import '../../main_screen.dart';
|
||||
import 'base_library_tab.dart';
|
||||
@@ -147,25 +148,20 @@ class _LibraryCollectionsTabState extends BaseLibraryTabState<MediaItem, Library
|
||||
Widget _buildGridSliver(int density, {required bool fullCardLayout}) {
|
||||
return SliverPadding(
|
||||
padding: _effectivePadding,
|
||||
sliver: SliverLayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxCrossAxisExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, density);
|
||||
final gridSpacing = MediaGridDelegate.spacingFor(context: context, fullBleedImage: fullCardLayout);
|
||||
final columnCount = GridSizeCalculator.getColumnCount(
|
||||
constraints.crossAxisExtent,
|
||||
maxCrossAxisExtent,
|
||||
crossAxisSpacing: gridSpacing,
|
||||
sliver: SliverCrossAxisLayoutBuilder(
|
||||
builder: (context, crossAxisExtent) {
|
||||
final geometry = MediaGridGeometry.resolve(
|
||||
context: context,
|
||||
crossAxisExtent: crossAxisExtent,
|
||||
density: density,
|
||||
fullBleedImage: fullCardLayout,
|
||||
);
|
||||
return SliverGrid.builder(
|
||||
gridDelegate: MediaGridDelegate.createDelegate(
|
||||
context: context,
|
||||
density: density,
|
||||
fullBleedImage: fullCardLayout,
|
||||
),
|
||||
gridDelegate: geometry.delegate,
|
||||
itemCount: totalSize,
|
||||
itemBuilder: (context, index) => _buildMediaCardItem(
|
||||
index,
|
||||
isFirstColumn: GridSizeCalculator.isFirstColumn(index, columnCount),
|
||||
isFirstColumn: GridSizeCalculator.isFirstColumn(index, geometry.columnCount),
|
||||
fullBleedImage: fullCardLayout,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import '../../../widgets/focusable_media_card.dart';
|
||||
import '../../../widgets/media_grid_delegate.dart';
|
||||
import '../../../widgets/settings_builder.dart';
|
||||
import '../../../widgets/skeleton_media_card.dart';
|
||||
import '../../../widgets/sliver_cross_axis_layout_builder.dart';
|
||||
import '../../../i18n/strings.g.dart';
|
||||
import '../../main_screen.dart';
|
||||
import 'base_library_tab.dart';
|
||||
@@ -149,25 +150,20 @@ class _LibraryPlaylistsTabState extends BaseLibraryTabState<MediaPlaylist, Libra
|
||||
Widget _buildGridSliver(int density, {required bool fullCardLayout}) {
|
||||
return SliverPadding(
|
||||
padding: _effectivePadding,
|
||||
sliver: SliverLayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxCrossAxisExtent = GridSizeCalculator.getMaxCrossAxisExtent(context, density);
|
||||
final gridSpacing = MediaGridDelegate.spacingFor(context: context, fullBleedImage: fullCardLayout);
|
||||
final columnCount = GridSizeCalculator.getColumnCount(
|
||||
constraints.crossAxisExtent,
|
||||
maxCrossAxisExtent,
|
||||
crossAxisSpacing: gridSpacing,
|
||||
sliver: SliverCrossAxisLayoutBuilder(
|
||||
builder: (context, crossAxisExtent) {
|
||||
final geometry = MediaGridGeometry.resolve(
|
||||
context: context,
|
||||
crossAxisExtent: crossAxisExtent,
|
||||
density: density,
|
||||
fullBleedImage: fullCardLayout,
|
||||
);
|
||||
return SliverGrid.builder(
|
||||
gridDelegate: MediaGridDelegate.createDelegate(
|
||||
context: context,
|
||||
density: density,
|
||||
fullBleedImage: fullCardLayout,
|
||||
),
|
||||
gridDelegate: geometry.delegate,
|
||||
itemCount: totalSize,
|
||||
itemBuilder: (context, index) => _buildPlaylistCard(
|
||||
index,
|
||||
isFirstColumn: GridSizeCalculator.isFirstColumn(index, columnCount),
|
||||
isFirstColumn: GridSizeCalculator.isFirstColumn(index, geometry.columnCount),
|
||||
fullBleedImage: fullCardLayout,
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user