diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index 2a2345b2..bc447fa0 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -28,6 +28,7 @@ import '../mixins/watch_state_aware.dart'; import '../utils/watch_state_notifier.dart'; import '../utils/app_logger.dart'; import '../utils/provider_extensions.dart'; +import 'main_screen.dart'; import '../utils/video_player_navigation.dart'; import '../utils/layout_constants.dart'; import '../utils/platform_detector.dart'; @@ -1039,6 +1040,10 @@ class _DiscoverScreenState extends State onRemoveFromContinueWatching: _refreshContinueWatching, isInContinueWatching: true, onVerticalNavigation: (isUp) => _handleVerticalNavigation(0, isUp), + onNavigateUp: () { + _heroFocusNode.requestFocus(); + _scrollController.animateTo(0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut); + }, ), ), @@ -1053,6 +1058,10 @@ class _DiscoverScreenState extends State onRefresh: updateItem, // Hub index is i + 1 if continue watching exists, otherwise i onVerticalNavigation: (isUp) => _handleVerticalNavigation(_onDeck.isNotEmpty ? i + 1 : i, isUp), + onNavigateUp: (i == 0 && _onDeck.isEmpty) ? () { + _heroFocusNode.requestFocus(); + _scrollController.animateTo(0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut); + } : null, ), ), diff --git a/lib/screens/libraries/tabs/library_recommended_tab.dart b/lib/screens/libraries/tabs/library_recommended_tab.dart index fac2fb7d..58e981fe 100644 --- a/lib/screens/libraries/tabs/library_recommended_tab.dart +++ b/lib/screens/libraries/tabs/library_recommended_tab.dart @@ -115,8 +115,13 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState= _hubKeys.length) { - // At boundary, block navigation + if (targetIndex < 0) { + // At top boundary - return false to allow onNavigateUp to handle it + return false; + } + + if (targetIndex >= _hubKeys.length) { + // At bottom boundary, block navigation return true; } @@ -158,6 +163,7 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState _handleVerticalNavigation(index, isUp), onBack: widget.onBack, + onNavigateUp: index == 0 ? widget.onBack : null, ); }, ); diff --git a/lib/widgets/hub_section.dart b/lib/widgets/hub_section.dart index 59b16af9..e53cd2c8 100644 --- a/lib/widgets/hub_section.dart +++ b/lib/widgets/hub_section.dart @@ -39,6 +39,10 @@ class HubSection extends StatefulWidget { /// Used to navigate focus back to the tab bar. final VoidCallback? onBack; + /// Called when the user presses UP while at the topmost item (first hub). + /// Used to navigate focus to the tab bar. + final VoidCallback? onNavigateUp; + const HubSection({ super.key, required this.hub, @@ -49,6 +53,7 @@ class HubSection extends StatefulWidget { this.showServerName = false, this.onVerticalNavigation, this.onBack, + this.onNavigateUp, }); @override @@ -173,6 +178,7 @@ class HubSectionState extends State { _scrollToIndex(_focusedIndex); setState(() {}); } + // At leftmost item: do nothing, but consume event to prevent focus escape return KeyEventResult.handled; } @@ -189,7 +195,11 @@ class HubSectionState extends State { // Up/Down: delegate to parent for vertical hub navigation, ALWAYS consume if (key.isUpKey) { - widget.onVerticalNavigation?.call(true); + final handled = widget.onVerticalNavigation?.call(true) ?? false; + // If not handled (at top boundary) and we have onNavigateUp, call it + if (!handled && widget.onNavigateUp != null) { + widget.onNavigateUp!(); + } return KeyEventResult.handled; } if (key.isDownKey) {