Merge branch 'feature/dpad-navigation'

This commit is contained in:
edde746
2026-01-20 22:06:07 +01:00
3 changed files with 28 additions and 3 deletions
+9
View File
@@ -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<DiscoverScreen>
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<DiscoverScreen>
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,
),
),
@@ -115,8 +115,13 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<PlexHub, LibraryRe
final targetIndex = isUp ? hubIndex - 1 : hubIndex + 1;
// Check if target is valid
if (targetIndex < 0 || targetIndex >= _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<PlexHub, LibraryRe
onRemoveFromContinueWatching: isContinueWatching ? _refreshContinueWatching : null,
onVerticalNavigation: (isUp) => _handleVerticalNavigation(index, isUp),
onBack: widget.onBack,
onNavigateUp: index == 0 ? widget.onBack : null,
);
},
);
+11 -1
View File
@@ -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<HubSection> {
_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<HubSection> {
// 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) {