diff --git a/lib/ui/screens/browse/favorites_screen.dart b/lib/ui/screens/browse/favorites_screen.dart index e70679afe..92f8a8334 100644 --- a/lib/ui/screens/browse/favorites_screen.dart +++ b/lib/ui/screens/browse/favorites_screen.dart @@ -104,10 +104,14 @@ class _FavoritesScreenState extends State with GridFocusNodeMix return _vm.rowItems[types[_selectedTab]] ?? const []; } + /// The list the grid focus nodes are keyed on. + List get _focusTrackedItems => + _vm.viewStyle == FavoritesViewStyle.library + ? _vm.gridItems + : _currentTabItems(); + void _maybeBumpGridVersion() { - final current = _vm.viewStyle == FavoritesViewStyle.library - ? _vm.gridItems - : _currentTabItems(); + final current = _focusTrackedItems; final length = current.length; final firstId = length == 0 ? null : current.first.id; if (length != _lastGridItemsLength || firstId != _lastGridFirstItemId) { @@ -353,7 +357,11 @@ class _FavoritesScreenState extends State with GridFocusNodeMix _vm.viewStyle == FavoritesViewStyle.home ? _tabsFocusNode : null, child: QuickReturnWrapper( scrollController: _scrollController, - topFocusNode: getGridItemFocusNode(0), + // cleanupGridFocusNodes disposes node 0 once the list empties, and the + // wrapper would keep holding it, so pass nothing instead. + topFocusNode: _focusTrackedItems.isNotEmpty + ? getGridItemFocusNode(0) + : null, child: _buildContent(context), ), ); diff --git a/lib/ui/screens/browse/library_browse_screen.dart b/lib/ui/screens/browse/library_browse_screen.dart index cf94d370a..d3b298fbc 100644 --- a/lib/ui/screens/browse/library_browse_screen.dart +++ b/lib/ui/screens/browse/library_browse_screen.dart @@ -208,6 +208,7 @@ class _LibraryBrowseScreenState extends State /// Whether the scroll view has settled metrics and is within /// [_kLoadMoreExtent] of its end. bool get _nearGridEnd { + if (!_scrollController.hasClients) return false; // Two grids briefly share the controller while one is swapped out. if (_scrollController.positions.length != 1) return false; final pos = _scrollController.position; @@ -729,7 +730,9 @@ class _LibraryBrowseScreenState extends State _vm.scrollDirection == LibraryScrollDirection.horizontal ? Axis.horizontal : Axis.vertical, - topFocusNode: getGridItemFocusNode(0), + // cleanupGridFocusNodes disposes node 0 once the list empties, and + // the wrapper would keep holding it, so pass nothing instead. + topFocusNode: _vm.items.isNotEmpty ? getGridItemFocusNode(0) : null, child: _buildContent(context), ), ); @@ -1492,7 +1495,7 @@ class _LibraryBrowseScreenState extends State return Listener( onPointerSignal: (signal) { - if (signal is PointerScrollEvent) { + if (signal is PointerScrollEvent && _scrollController.hasClients) { final pos = _scrollController.position; final newOffset = (_scrollController.offset + signal.scrollDelta.dy) @@ -1518,6 +1521,9 @@ class _LibraryBrowseScreenState extends State ), delegate: SliverChildBuilderDelegate( (context, index) { + // No childCount on this delegate, so returning null is + // what tells the sliver where the list ends. + if (index >= _vm.items.length) return null; final item = _vm.items[index]; return MediaCard( title: item.name,