Make player controls D-pad friendly#167
Make player controls D-pad friendly#167google-labs-jules[bot] wants to merge 79 commits intomainfrom
Conversation
- Modified the `setAccount` method to automatically select the first non-anonymous account as the default for all account types. - Removed the account selection dialog that appeared after a successful login, streamlining the user experience.
Auto-select Default Account After Login
Reorders the `DynamicsTabType` enum to move the 'All' tab to the end of the list. This aligns with user feedback to prioritize specific content categories over the general 'All' view, improving the user experience. The tab order is now: 投稿 → 番剧 → 专栏 → UP → 全部. No UI code changes were necessary as the tab bar and tab view are dynamically generated from the enum's values.
Sets the initial index of the TabController on the login page to 2, making the QR code scanning tab the default view when the page is loaded. This improves user experience for those who primarily use QR code login.
Calls `_handleTabChange()` in the `onInit` method to ensure that the QR code is generated and displayed when the login page is first loaded with the QR code tab as the default. This fixes a bug where the QR code would not appear until the user manually switched tabs.
Set QR Code as Default Login Tab
Simplifies the `canPop` logic in the `PopScope` widget to only check for fullscreen mode. This resolves an issue where the Android TV remote could not navigate back from the video detail page. The previous logic was too restrictive for devices that are always in landscape mode. This change improves the user experience on both Android TV and mobile devices by making the back button behavior more predictable.
This commit addresses a regression introduced in the previous commit. While the back button navigation was fixed, the "OK" button on the Android TV remote lost its play/pause functionality. The `PlayerFocus` widget was incorrectly mapping the `enter` key to the `onSendDanmaku` function. This change remaps both the `enter` and `select` keys to the play/pause action, restoring the expected behavior for the "OK" button on Android TV remotes.
This commit implements playlist navigation using the up and down arrow keys on the remote control. This provides a more intuitive experience for users on Android TV. The up and down arrow keys are now used to switch to the previous and next videos in a playlist, respectively. The previous volume control functionality has been removed from these keys to accommodate this change.
Fix Video Detail Page Back Navigation
Modified the video player controller to prevent the controls from auto-hiding when playback is paused. This improves usability on remote-controlled devices like Android TV where persistent on-screen controls are necessary for navigation. Key changes: - When the player state changes to paused, the controls are now explicitly made visible. - The auto-hide timer logic in `hideTaskControls` now checks if the player is paused before hiding the controls. - The `controls` setter only starts the auto-hide timer if the video is actively playing. - Tapping the screen no longer hides the controls when the video is paused.
Wrapped the VideoPopupMenu in VideoCardH and VideoCardV with the ExcludeFocus widget. This prevents the menu button from gaining focus during D-pad navigation on Android TV, ensuring that focus only cycles between the video cards themselves.
- Duplicated the "Submissions" tab to isolate style changes. - Applied a new grid-based layout to the new tab. - Hid the original "Submissions" tab to avoid duplication. - Fixed a critical bug where the "remove" functionality was broken. - Refactored the code to improve readability and reduce duplication.
- Duplicated the "Submissions" tab to isolate style changes. - Applied a new grid-based layout to the new tab. - Hid the original "Submissions" tab to avoid duplication. - Fixed a critical bug where the "remove" functionality was broken. - Refactored the code to improve readability and reduce duplication. - Recreated the missing `DynamicToRecVideoAdapter` to fix the build.
…ause fix: Keep player controls visible when paused
Android TV Focus Navigation Improvement
- Duplicated the "Submissions" tab to isolate style changes. - Applied a new grid-based layout to the new tab. - Hid the original "Submissions" tab to avoid duplication. - Fixed a critical bug where the "remove" functionality was broken. - Refactored the code to improve readability and reduce duplication. - Recreated the missing `DynamicToRecVideoAdapter` to fix the build. - Resolved `Stat` class conflict using a prefixed import. - Fixed null-safety errors and undefined method calls.
- Duplicated the "Submissions" tab to isolate style changes. - Applied a new grid-based layout to the new tab. - Hid the original "Submissions" tab to avoid duplication. - Fixed a critical bug where the "remove" functionality was broken. - Refactored the code to improve readability and reduce duplication. - Recreated the missing `DynamicToRecVideoAdapter` to fix the build. - Resolved `Stat` class conflict using a prefixed import. - Fixed null-safety errors and undefined method calls. - Added missing properties to data models.
- Duplicated the "Submissions" tab to isolate style changes. - Applied a new grid-based layout to the new tab. - Hid the original "Submissions" tab to avoid duplication. - Fixed a critical bug where the "remove" functionality was broken. - Refactored the code to improve readability and reduce duplication. - Recreated the missing `DynamicToRecVideoAdapter` to fix the build. - Resolved `Stat` class conflict using a prefixed import. - Fixed null-safety errors and undefined method calls. - Added missing properties to data models. - Filtered non-video content from the "Submissions" tab.
…-tab-1 Isolate Dynamics Submission Tab Style
Updates a wide range of default settings based on user feedback to improve the out-of-the-box experience for new users. Modified settings include: - Player: Disabled fullscreen lock/screenshot buttons, enabled auto-fullscreen, and hid action items. - Dynamics: Set UP panel to top, hid unread badges. - Startup: Changed default page to Dynamics. - Extensions: Enabled SponsorBlock, AI summary, and other QoL features. - Filtering: Set minimum recommended video duration to 90s.
Refactored the Dynamics page to dynamically calculate tab indices instead of relying on hardcoded values. This fixes a bug where clicking a user avatar would navigate to the wrong tab after the "All" and "Following" tabs were reordered.
Updates a wide range of default settings based on user feedback to improve the out-of-the-box experience for new users. Modified settings include: - Player: Disabled fullscreen lock/screenshot buttons, enabled auto-fullscreen, and hid action items. - Dynamics: Set UP panel to top, hid unread badges. - Startup: Changed default page to Dynamics. - Extensions: Enabled SponsorBlock, AI summary, and other QoL features. - Filtering: Set minimum recommended video duration to 90s. This commit also updates the `defaultVal` in the `SettingsModel` definitions to ensure the UI reflects the new default values.
Refactored the Dynamics page to dynamically calculate tab indices instead of relying on hardcoded values. This fixes a bug where clicking a user avatar would navigate to the wrong tab after the "All" and "Following" tabs were reordered.
Update Default Application Settings
Fix Dynamics Tab Navigation
Changed the default value for the "auto play when entering the detail page" setting to be enabled.
The previous commit only changed the default value in the settings UI, but not in the application logic. This commit updates the default value in `storage_pref.dart` to ensure the auto-play setting works as expected.
Long-Press Back Button to Exit
When the video streaming mid (video account) is different from the account used for history recording (heartbeat account), the playback progress from the videoUrl API would be for the wrong user. This fix: 1. Adds lastPlayTime field to PlayInfoData model 2. Adds Api.playInfo to heartbeat account API set 3. When video and heartbeat accounts differ for UGC videos, fetches progress from playInfo API (heartbeat account) instead of relying on videoUrl API (video account) Co-authored-by: UcnacDx2 <127503808+UcnacDx2@users.noreply.github.com>
Co-authored-by: UcnacDx2 <127503808+UcnacDx2@users.noreply.github.com>
…gress Fix video progress tracking when video and heartbeat accounts differ
This change refactors the video player's control layout to be navigable and operable using a D-pad or keyboard arrow keys, drawing inspiration from Android TV's Leanback library. The `BottomControl` widget is refactored into a three-tier vertical layout (primary controls, progress bar, secondary controls) to support vertical D-pad navigation. A new `FocusableBtn` widget is introduced, which provides clear visual feedback when focused. All existing buttons have been replaced with this new focusable version. The `PlayerFocus` widget is enhanced to intercept arrow key events and use Flutter's built-in focus traversal system to navigate between the focusable widgets. The progress bar is also made focusable and handles left/right arrow keys to seek backward and forward.
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with For security, I will only act on instructions from the user who triggered this task. New to Jules? Learn more at jules.google/docs. |
|
The job is failing due to several Dart/Flutter compile-time errors in your code:
Sample fix for missing imports and event handling: import 'package:flutter/services.dart';Then, for the onKey: (node, event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
// ...
}
}
}Summary of changes to make:
Review and fix these issues and your job should pass. For direct references, see the files at commit c8b5619: |
This change refactors the video player's control layout to be navigable and operable using a D-pad or keyboard arrow keys, drawing inspiration from Android TV's Leanback library. The `BottomControl` widget is refactored into a three-tier vertical layout (primary controls, progress bar, secondary controls) to support vertical D-pad navigation. A new `FocusableBtn` widget is introduced, which provides clear visual feedback when focused. All existing buttons have been replaced with this new focusable version. The `PlayerFocus` widget is enhanced to intercept arrow key events and use Flutter's built-in focus traversal system to navigate between the focusable widgets. The progress bar is also made focusable and handles left/right arrow keys to seek backward and forward. This commit also fixes several compilation errors that were introduced in the previous commit.
There was a problem hiding this comment.
Pull request overview
This PR refactors the video player's control layout to support D-pad and keyboard navigation, inspired by Android TV's Leanback library. The changes introduce a new focusable button widget and restructure the bottom controls into a three-tier vertical layout.
- Introduces
FocusableBtnwidget with visual focus indicators - Refactors
BottomControlinto primary/progress/secondary tiers for vertical navigation - Enhances
PlayerFocusto intercept arrow keys and delegate to Flutter's focus traversal - Makes the progress bar focusable with left/right arrow seeking
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/plugin/pl_player/widgets/focusable_btn.dart | New widget providing visual focus feedback for buttons |
| lib/plugin/pl_player/widgets/play_pause_btn.dart | Updated to use the new FocusableBtn widget |
| lib/plugin/pl_player/widgets/bottom_control.dart | Refactored from function-based to widget-based layout with focusable controls and progress bar |
| lib/plugin/pl_player/view.dart | Removes the now-refactored buildBottomControl method |
| lib/pages/video/widgets/player_focus.dart | Enhanced with arrow key handling for focus traversal |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -87,27 +120,57 @@ class BottomControl extends StatelessWidget { | |||
| } | |||
|
|
|||
| Widget progressBar() { | |||
There was a problem hiding this comment.
The FocusNode created on line 122 is never disposed, which will cause a memory leak. Since BottomControl is a StatelessWidget, the progressBar() method gets called on every rebuild, creating a new FocusNode each time without disposing the previous ones. Consider converting BottomControl to a StatefulWidget and properly managing the lifecycle of the FocusNode, or use a stateful widget for the progress bar portion.
| }, | ||
| child: DecoratedBox( | ||
| decoration: BoxDecoration( | ||
| border: focusNode.hasFocus | ||
| ? Border.all(color: Colors.white, width: 2) | ||
| : null, |
There was a problem hiding this comment.
The DecoratedBox uses focusNode.hasFocus directly to determine the border decoration, but hasFocus is not a reactive property. This means the border won't update when the focus state changes. The widget needs to rebuild when focus changes. Consider wrapping this in a StatefulWidget and adding a listener to the FocusNode to trigger rebuilds, similar to how it's done in FocusableBtn.
| onSeek: (e) => onSeek(e, max), | ||
| return Focus( | ||
| focusNode: focusNode, | ||
| onKey: (node, event) { |
There was a problem hiding this comment.
Using the deprecated RawKeyDownEvent class. Flutter deprecated the RawKeyboard API in favor of the new hardware keyboard API. Use KeyDownEvent instead, which is already used in player_focus.dart (line 51). This ensures consistency and future compatibility.
| final child = SizedBox( | ||
| width: widget.width, | ||
| height: widget.height, | ||
| child: InkWell( | ||
| onTap: widget.onTap, | ||
| onLongPress: widget.onLongPress, | ||
| onSecondaryTap: widget.onSecondaryTap, | ||
| child: DecoratedBox( | ||
| decoration: BoxDecoration( | ||
| border: _isFocused | ||
| ? Border.all(color: Colors.white, width: 2) | ||
| : null, | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: widget.icon, | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| if (widget.tooltip != null) { | ||
| return Tooltip(message: widget.tooltip!, child: child); | ||
| } | ||
| return child; |
There was a problem hiding this comment.
The FocusableBtn widget doesn't wrap its content in a Focus widget, so it cannot actually receive keyboard focus or participate in focus traversal. The FocusNode is created and listeners are attached, but the widget tree doesn't connect it to Flutter's focus system. Wrap the InkWell or the entire child in a Focus widget with focusNode: _focusNode to make it actually focusable via keyboard navigation.
| final child = SizedBox( | |
| width: widget.width, | |
| height: widget.height, | |
| child: InkWell( | |
| onTap: widget.onTap, | |
| onLongPress: widget.onLongPress, | |
| onSecondaryTap: widget.onSecondaryTap, | |
| child: DecoratedBox( | |
| decoration: BoxDecoration( | |
| border: _isFocused | |
| ? Border.all(color: Colors.white, width: 2) | |
| : null, | |
| borderRadius: BorderRadius.circular(4), | |
| ), | |
| child: widget.icon, | |
| ), | |
| ), | |
| ); | |
| if (widget.tooltip != null) { | |
| return Tooltip(message: widget.tooltip!, child: child); | |
| } | |
| return child; | |
| final focusableChild = Focus( | |
| focusNode: _focusNode, | |
| child: SizedBox( | |
| width: widget.width, | |
| height: widget.height, | |
| child: InkWell( | |
| onTap: widget.onTap, | |
| onLongPress: widget.onLongPress, | |
| onSecondaryTap: widget.onSecondaryTap, | |
| child: DecoratedBox( | |
| decoration: BoxDecoration( | |
| border: _isFocused | |
| ? Border.all(color: Colors.white, width: 2) | |
| : null, | |
| borderRadius: BorderRadius.circular(4), | |
| ), | |
| child: widget.icon, | |
| ), | |
| ), | |
| ), | |
| ); | |
| if (widget.tooltip != null) { | |
| return Tooltip(message: widget.tooltip!, child: focusableChild); | |
| } | |
| return focusableChild; |
| child: InkWell( | ||
| onTap: widget.onTap, | ||
| onLongPress: widget.onLongPress, | ||
| onSecondaryTap: widget.onSecondaryTap, | ||
| child: DecoratedBox( | ||
| decoration: BoxDecoration( | ||
| border: _isFocused | ||
| ? Border.all(color: Colors.white, width: 2) | ||
| : null, | ||
| borderRadius: BorderRadius.circular(4), | ||
| ), | ||
| child: widget.icon, | ||
| ), | ||
| ), |
There was a problem hiding this comment.
The InkWell widget doesn't receive keyboard activation events (Enter/Space key presses) because it's not wrapped in a Focus widget. Users won't be able to activate the button via keyboard when it's focused. To support keyboard activation, either wrap the widget in a Focus widget with onKeyEvent handler, or use InkWell's autofocus and focusNode properties in conjunction with a proper Focus wrapper.
| return Focus( | ||
| focusNode: focusNode, | ||
| onKey: (node, event) { | ||
| if (event is KeyDownEvent) { |
There was a problem hiding this comment.
The code uses RawKeyDownEvent and LogicalKeyboardKey but doesn't import them. These classes are from package:flutter/services.dart. Add the missing import statement at the top of the file.
| if (event is KeyDownEvent) { | |
| if (event is RawKeyDownEvent) { |
|
把发弹幕,开启弹幕和画中画和更多菜单的那排按钮也挪下来 |
This change refactors the video player's control layout to be navigable and operable using a D-pad or keyboard arrow keys, drawing inspiration from Android TV's Leanback library. The `BottomControl` widget is refactored into a three-tier vertical layout (primary controls, progress bar, secondary controls) to support vertical D-pad navigation. A new `FocusableBtn` widget is introduced, which provides clear visual feedback when focused. All existing buttons have been replaced with this new focusable version. The `PlayerFocus` widget is enhanced to intercept arrow key events and use Flutter's built-in focus traversal system to navigate between the focusable widgets. The progress bar is also made focusable and handles left/right arrow keys to seek backward and forward. This commit also fixes several compilation errors that were introduced in the previous commit, and moves the "send danmaku," "toggle danmaku," "picture-in-picture," and "more menu" buttons to the bottom control bar.
|
The job failed due to two errors in lib/plugin/pl_player/widgets/bottom_control.dart:
Solutions:
Example Fix for line 882: onTap: showSettingSheet,If Review the lines in the file: Correcting these should resolve the build failure. |
This commit refactors the video player controls to be more friendly for D-Pad (remote/keyboard) navigation. The implementation is inspired by the Android Leanback layout philosophy. Key changes: - Restructured the control layout in `bottom_control.dart` into a three-row vertical layout (primary controls, progress bar, secondary controls). - Created a reusable `FocusableBtn` widget to make all control buttons focusable. - Implemented D-Pad navigation logic in `player_focus.dart` to handle arrow key traversal between focusable elements. - Made the progress bar focusable and implemented seeking with left/right arrow keys. - Moved the danmaku, PiP, and "more" buttons from `header_control.dart` to `bottom_control.dart` to consolidate all controls in one place. - Cleaned up `header_control.dart` by removing the now-unused methods and imports.
|
The job failed because of missing or incorrectly referenced classes, methods, and parameters in the Dart/Flutter code. Specifically, the following issues were reported: Key Errors and Solutions
What to do
Example FixIf you previously had: DpadRegionScope(
child: IconButton(
icon: Icon(Icons.play_arrow),
),
)But
If an IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () {
// your callback
},
)If import 'package:flutter/widgets.dart'; // if it's from Flutter SDKOr define it if it’s a custom enum. Summary
Correct these issues in your Dart files where the errors are reported. This will resolve the build failure. If you need specific code for one of the errors, let me know which file and line to focus on. |
This commit refactors the video player controls to be more friendly for D-Pad (remote/keyboard) navigation. The implementation is inspired by the Android Leanback layout philosophy. Key changes: - Restructured the control layout in `bottom_control.dart` into a three-row vertical layout (primary controls, progress bar, secondary controls). - Created a reusable `FocusableBtn` widget to make all control buttons focusable. - Implemented D-Pad navigation logic in `player_focus.dart` to handle arrow key traversal between focusable elements. - Made the progress bar focusable and implemented seeking with left/right arrow keys. - Moved the danmaku, PiP, and "more" buttons from `header_control.dart` to `bottom_control.dart` to consolidate all controls in one place. - Cleaned up `header_control.dart` by removing the now-unused methods and imports. Fixes build errors by: - Adding the missing `flutter/widgets.dart` import in `player_focus.dart`. - Adding the missing `onPressed` parameter to an `IconButton` in `video/view.dart`.
|
GitHub Copilot Chat Assistant Cause
What to do (step-by-step, actionable)
Example minimal patch (illustrative)
@@
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+// >>> Added to fix missing types referenced below:
+import 'package:hive/hive.dart'; // Box
+import 'package:dio/dio.dart'; // Request/Options/ResponseType etc if used unprefixed
+// import the real locations for these types in your repo:
+import 'package:PiliPlus/plugin/pl_player/models/danmaku.dart';
+import 'package:PiliPlus/pages/video/models/play_url_model.dart';
+import 'package:PiliPlus/common/widgets/action_row_item.dart';
+import 'package:PiliPlus/common/widgets/setting_items.dart';Why this will fix the CI failure
If you prefer, an ordered checklist to open a PR
References
If you want, I can (a) list the exact import paths for each missing symbol if you point me to where those types are declared in the repo, or (b) produce a ready-made PR patch if you confirm the file paths that contain VideoDanmaku, DanmakuItem, PlayUrlModel and the helper widgets. |
This commit refactors the video player controls to be more friendly for D-Pad (remote/keyboard) navigation. The implementation is inspired by the Android Leanback layout philosophy. Key changes: - Restructured the control layout in `bottom_control.dart` into a three-row vertical layout (primary controls, progress bar, secondary controls). - Created a reusable `FocusableBtn` widget to make all control buttons focusable. - Implemented D-Pad navigation logic in `player_focus.dart` to handle arrow key traversal between focusable elements. - Made the progress bar focusable and implemented seeking with left/right arrow keys. - Moved the danmaku, PiP, and "more" buttons from `header_control.dart` to `bottom_control.dart` to consolidate all controls in one place. - Cleaned up `header_control.dart` by removing the now-unused methods and imports. Fixes build errors by: - Adding the missing `flutter/widgets.dart` import in `player_focus.dart`. - Adding the missing `onPressed` parameter to an `IconButton` in `video/view.dart`. - Restoring missing imports to `bottom_control.dart`. - Fixing syntax and type errors in `bottom_control.dart`.
|
基于我们刚才确定的“高内聚低耦合”设计方案,以下是详细的实施计划。这个计划将分为四个阶段,从基础架构搭建到组件封装,最后是逻辑集成。 阶段一:基础架构搭建 (Infrastructure) 目标:建立焦点状态的管理中心,不破坏现有的业务逻辑。 创建 TVFocusManager 类 文件路径:lib/plugin/pl_player/logic/tv_focus_manager.dart (新建) 内容: 定义三个核心 FocusNode:seekBarNode, playButtonNode, qualityButtonNode。 实现 dispose() 方法。 可选优化:添加 debugLabel 以便调试。 集成到 PlPlayerController 文件路径:lib/plugin/pl_player/controller.dart 动作: 引入 TVFocusManager。 实例化 final TVFocusManager tvFocusManager = TVFocusManager();。 在 onClose 或 dispose 中调用 tvFocusManager.dispose()。 注意:确保 Controller 的生命周期管理正确,防止内存泄漏。 阶段二:核心组件封装 (Component Encapsulation) 目标:封装 UI 交互细节,让外部调用者无需关心“按下左键是快退还是切焦点”。 创建 InteractiveSeekBar (智能进度条) 文件路径:lib/plugin/pl_player/widgets/interactive_seek_bar.dart (新建) 逻辑实现: 接收 child (原本的 Slider) 和 controller。 使用 Focus 组件包裹。 onKeyEvent 处理: Left/Right: 调用 controller.seekRelative (快进快退),返回 handled。 OK/Enter: 调用 controller.onDoubleTapCenter (播放暂停),返回 handled。 Up/Down: 返回 ignored (让系统处理焦点导航)。 视觉反馈:监听 hasFocus,在 child 外层包裹带边框的 Container 或 DecoratedBox。 创建/升级 FocusableBtn (通用焦点按钮) 文件路径:lib/plugin/pl_player/widgets/focusable_btn.dart 逻辑实现: 接收 FocusNode 参数。 监听焦点状态变化。 当 hasFocus 为 true 时,显示白色边框(推荐使用 AnimatedContainer 做平滑过渡)。 关键点:确保按钮被点击 (onTap) 或按下确认键 (Enter) 时触发相同的回调。 阶段三:全局策略拦截 (Global Interception) 目标:实现“黑屏唤醒”和“特定入口跳转”的业务规则。 创建 TVEventInterceptor 文件路径:lib/plugin/pl_player/widgets/tv_event_interceptor.dart (新建) 逻辑实现: 包裹播放器整体视图。 在 onKeyEvent 中检查 controller.showControls.value (控制栏是否可见)。 如果不可见 (Hidden): OK: controller.controls = true -> tvFocusManager.seekBarNode.requestFocus() -> handled。 UP: controller.controls = true -> tvFocusManager.playButtonNode.requestFocus() -> handled。 DOWN: controller.controls = true -> tvFocusManager.qualityButtonNode.requestFocus() -> handled。 如果可见 (Visible): 返回 ignored (放行,让组件层处理)。 技巧:使用 addPostFrameCallback 确保 UI 渲染完成后再请求焦点。 阶段四:组装与集成 (Assembly) 目标:将上述模块组装到视图层,建立物理布局关系。 修改播放器主视图 文件路径:lib/pages/video/view.dart 或 lib/plugin/pl_player/view.dart 动作: 找到播放器的根 Widget(通常是 Stack)。 用 TVEventInterceptor 包裹整个 Player View。 重构底部控制栏布局 文件路径:lib/plugin/pl_player/widgets/bottom_control.dart 动作: 确保整体结构为纵向布局 (Column): Top Row (主要控制): 将播放/暂停按钮替换为使用 tvFocusManager.playButtonNode 的 FocusableBtn。 Middle: 将 Slider 用 InteractiveSeekBar 包裹,绑定 tvFocusManager.seekBarNode。 Bottom Row (次要控制): 将画质/选集按钮替换为使用 tvFocusManager.qualityButtonNode 的 FocusableBtn。 布局验证:确保这三部分在 Column 中是物理相邻的,这样 Flutter 的默认导航算法(FocusTraversal)会自动处理 Up/Down 切换。 验证与验收清单 (Checklist) 执行完上述步骤后,请按以下顺序测试: 唤醒测试: 播放中等待控制栏消失。 按 OK:控制栏应出现,进度条高亮,视频继续播放(未暂停)。 等待消失,按 上:控制栏出现,播放按钮高亮。 等待消失,按 下:控制栏出现,画质按钮高亮。 导航测试: 焦点在进度条时,按 上:焦点跳到播放按钮。 焦点在播放按钮时,按 下:焦点跳回进度条。 焦点在进度条时,按 下:焦点跳到画质按钮。 焦点在播放按钮行,按 左/右:焦点在同行的按钮间切换(如从播放跳到下一集)。 功能测试: 焦点在进度条时,按 左/右:视频进度改变,焦点不跑偏。 焦点在进度条时,按 OK:视频暂停/继续。 焦点在画质按钮时,按 OK:弹出画质菜单。 补充技巧 调试焦点:在 MaterialApp 中设置 debugShowCheckedModeBanner: false 之外,可以使用 FocusManager.instance.debugLabel 查看当前焦点的日志。 防止自动隐藏:在 controller.dart 的 hideTaskControls 逻辑中,务必保留之前提到的判断:if (hasFocus || isPaused) return;,防止用户操作到一半控制栏突然消失。 |
Refactors the player controls to be D-pad friendly, following the Leanback layout principles. - Creates a TVFocusManager to manage focus nodes. - Creates FocusableBtn and InteractiveSeekBar for focusable widgets. - Creates TVEventInterceptor to handle waking up the controls. - Refactors the bottom_control.dart to a three-row layout. - Updates player_focus.dart to handle D-pad navigation.
This change refactors the video player's control layout to be navigable and operable using a D-pad or keyboard arrow keys, drawing inspiration from Android TV's Leanback library.
The
BottomControlwidget is refactored into a three-tier vertical layout (primary controls, progress bar, secondary controls) to support vertical D-pad navigation. A newFocusableBtnwidget is introduced, which provides clear visual feedback when focused. All existing buttons have been replaced with this new focusable version.The
PlayerFocuswidget is enhanced to intercept arrow key events and use Flutter's built-in focus traversal system to navigate between the focusable widgets. The progress bar is also made focusable and handles left/right arrow keys to seek backward and forward.Fixes #163
PR created automatically by Jules for task 277104891523565276 started by @UcnacDx2