diff --git a/lib/screens/libraries/libraries_screen.dart b/lib/screens/libraries/libraries_screen.dart index f9bac924..dbfc097a 100644 --- a/lib/screens/libraries/libraries_screen.dart +++ b/lib/screens/libraries/libraries_screen.dart @@ -203,7 +203,12 @@ class _LibrariesScreenState extends State } if (tabState != null) { - (tabState as dynamic).focusFirstItem(); + // Browse tab has a chips bar - focus that first so DOWN navigates to grid + if (_tabController.index == 1) { + (tabState as dynamic).focusChipsBar(); + } else { + (tabState as dynamic).focusFirstItem(); + } } else { // State not available yet, retry after another frame WidgetsBinding.instance.addPostFrameCallback((_) { @@ -233,7 +238,12 @@ class _LibrariesScreenState extends State } if (tabState != null) { - (tabState as dynamic).focusFirstItem(); + // Browse tab has a chips bar - focus that first so DOWN navigates to grid + if (_tabController.index == 1) { + (tabState as dynamic).focusChipsBar(); + } else { + (tabState as dynamic).focusFirstItem(); + } } } diff --git a/lib/screens/libraries/tabs/library_browse_tab.dart b/lib/screens/libraries/tabs/library_browse_tab.dart index 6b3069f4..0a4cde49 100644 --- a/lib/screens/libraries/tabs/library_browse_tab.dart +++ b/lib/screens/libraries/tabs/library_browse_tab.dart @@ -82,15 +82,40 @@ class _LibraryBrowseTabState extends BaseLibraryTabState _gridItemFocusNodes = {}; + @override void dispose() { _cancelToken?.cancel(); _groupingChipFocusNode.dispose(); _filtersChipFocusNode.dispose(); _sortChipFocusNode.dispose(); + // Dispose all grid item focus nodes + for (final node in _gridItemFocusNodes.values) { + node.dispose(); + } + _gridItemFocusNodes.clear(); super.dispose(); } + /// Get or create a focus node for a grid item at the given index + FocusNode _getGridItemFocusNode(int index) { + return _gridItemFocusNodes.putIfAbsent(index, () => FocusNode(debugLabel: 'browse_grid_item_$index')); + } + + /// Clean up focus nodes for items that no longer exist + void _cleanupFocusNodes() { + final keysToRemove = _gridItemFocusNodes.keys.where((index) => index >= items.length).toList(); + for (final key in keysToRemove) { + _gridItemFocusNodes[key]?.dispose(); + _gridItemFocusNodes.remove(key); + } + } + // Override loadData to use our custom _loadContent @override Future> loadData() async { @@ -136,6 +161,36 @@ class _LibraryBrowseTabState extends BaseLibraryTabState _loadContent() async { // Cancel any pending request _cancelToken?.cancel(); @@ -219,6 +274,10 @@ class _LibraryBrowseTabState extends BaseLibraryTabState { ), ), - // Library items (hidden when collapsed to avoid clipped partial items) - if (_librariesExpanded && !isCollapsed) ...[ - const SizedBox(height: 4), - _isLoadingLibraries - ? Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: t.textMuted), - ), - ), - ) - : visibleLibraries.isEmpty - ? Padding( - padding: const EdgeInsets.all(16), - child: Text( - Translations.of(context).libraries.noLibrariesFound, - style: TextStyle(fontSize: 12, color: t.textMuted), - ), - ) - : _buildLibraryItems(visibleLibraries, t), - ], + // Library items with animated height + TweenAnimationBuilder( + tween: Tween(end: (_librariesExpanded && !isCollapsed) ? 1.0 : 0.0), + duration: tokens(context).normal, + curve: Curves.easeOutCubic, + builder: (context, value, child) { + return ClipRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: value, + child: child, + ), + ); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + _isLoadingLibraries + ? Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2, color: t.textMuted), + ), + ), + ) + : visibleLibraries.isEmpty + ? Padding( + padding: const EdgeInsets.all(16), + child: Text( + Translations.of(context).libraries.noLibrariesFound, + style: TextStyle(fontSize: 12, color: t.textMuted), + ), + ) + : _buildLibraryItems(visibleLibraries, t), + ], + ), + ), ], ); }