diff --git a/lib/screens/libraries/libraries_screen.dart b/lib/screens/libraries/libraries_screen.dart index d370670f..de88cd09 100644 --- a/lib/screens/libraries/libraries_screen.dart +++ b/lib/screens/libraries/libraries_screen.dart @@ -219,6 +219,9 @@ class _LibrariesScreenState extends State if (tabController.indexIsChanging) { return; } + // On mobile (touch mode), skip auto-focus to prevent ensureVisible() + // from interfering with TabBarView page animations + if (!InputModeTracker.isKeyboardMode(context)) return; // Re-enable auto-focus since user is navigating into tab content // Only call setState if the value actually changes to avoid unnecessary rebuilds @@ -1064,38 +1067,50 @@ class _LibrariesScreenState extends State // Disable swipe on desktop - trackpad scrolling triggers accidental tab switches // See: https://github.com/flutter/flutter/issues/11132 physics: PlatformDetector.isDesktop(context) ? const NeverScrollableScrollPhysics() : null, + // Wrap each tab in ClipRect so horizontal overflow (e.g. hub rows + // with Clip.none) doesn't bleed into adjacent tabs during swipe transitions. + // The TabBarView's own clipBehavior only clips at the viewport level, + // not per-page, so we need per-child clipping. children: [ - LibraryRecommendedTab( - key: _recommendedTabKey, - library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), - isActive: tabController.index == 0, - suppressAutoFocus: suppressAutoFocus, - onDataLoaded: () => _handleTabDataLoaded(0), - onBack: focusTabBar, + ClipRect( + child: LibraryRecommendedTab( + key: _recommendedTabKey, + library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), + isActive: tabController.index == 0, + suppressAutoFocus: suppressAutoFocus, + onDataLoaded: () => _handleTabDataLoaded(0), + onBack: focusTabBar, + ), ), - LibraryBrowseTab( - key: _browseTabKey, - library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), - isActive: tabController.index == 1, - suppressAutoFocus: suppressAutoFocus, - onDataLoaded: () => _handleTabDataLoaded(1), - onBack: focusTabBar, + ClipRect( + child: LibraryBrowseTab( + key: _browseTabKey, + library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), + isActive: tabController.index == 1, + suppressAutoFocus: suppressAutoFocus, + onDataLoaded: () => _handleTabDataLoaded(1), + onBack: focusTabBar, + ), ), - LibraryCollectionsTab( - key: _collectionsTabKey, - library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), - isActive: tabController.index == 2, - suppressAutoFocus: suppressAutoFocus, - onDataLoaded: () => _handleTabDataLoaded(2), - onBack: focusTabBar, + ClipRect( + child: LibraryCollectionsTab( + key: _collectionsTabKey, + library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), + isActive: tabController.index == 2, + suppressAutoFocus: suppressAutoFocus, + onDataLoaded: () => _handleTabDataLoaded(2), + onBack: focusTabBar, + ), ), - LibraryPlaylistsTab( - key: _playlistsTabKey, - library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), - isActive: tabController.index == 3, - suppressAutoFocus: suppressAutoFocus, - onDataLoaded: () => _handleTabDataLoaded(3), - onBack: focusTabBar, + ClipRect( + child: LibraryPlaylistsTab( + key: _playlistsTabKey, + library: allLibraries.firstWhere((lib) => lib.globalKey == _selectedLibraryGlobalKey), + isActive: tabController.index == 3, + suppressAutoFocus: suppressAutoFocus, + onDataLoaded: () => _handleTabDataLoaded(3), + onBack: focusTabBar, + ), ), ], ), diff --git a/lib/screens/libraries/tabs/base_library_tab.dart b/lib/screens/libraries/tabs/base_library_tab.dart index ca588ea7..ade589f8 100644 --- a/lib/screens/libraries/tabs/base_library_tab.dart +++ b/lib/screens/libraries/tabs/base_library_tab.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../../../focus/input_mode_tracker.dart'; import '../../../models/plex_library.dart'; import '../../../utils/app_logger.dart'; import '../../../mixins/library_tab_state.dart'; @@ -164,6 +165,9 @@ abstract class BaseLibraryTabState> extends State void tryFocus() { // Don't auto-focus if suppressed (e.g., when navigating via tab bar) if (widget.suppressAutoFocus) return; + // On mobile (touch mode), skip auto-focus to prevent ensureVisible() + // from interfering with TabBarView page animations + if (!InputModeTracker.isKeyboardMode(context)) return; if (widget.isActive && _hasLoadedData && !hasFocused && _items.isNotEmpty) { hasFocused = true; diff --git a/lib/screens/libraries/tabs/library_browse_tab.dart b/lib/screens/libraries/tabs/library_browse_tab.dart index 05247a6a..a2afc36c 100644 --- a/lib/screens/libraries/tabs/library_browse_tab.dart +++ b/lib/screens/libraries/tabs/library_browse_tab.dart @@ -5,6 +5,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:dio/dio.dart'; import '../../../focus/dpad_navigator.dart'; +import '../../../focus/input_mode_tracker.dart'; import '../../../../services/plex_client.dart'; import '../../../models/plex_metadata.dart'; import '../../../models/plex_filter.dart'; @@ -224,6 +225,9 @@ class _LibraryBrowseTabState extends BaseLibraryTabState