From b3f0622c86763a56c1ddc323bd2c0acdab91ee2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:04:52 +0000 Subject: [PATCH 1/5] Initial plan From 9259a1cd4c8944e922d32a4d94cd0b3a05f078d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:12:49 +0000 Subject: [PATCH 2/5] Add more options menu to live room interface Co-authored-by: UcnacDx2 <127503808+UcnacDx2@users.noreply.github.com> --- lib/pages/live_room/view.dart | 9 +- .../live_room/widgets/header_control.dart | 358 +++++++++++++++++- 2 files changed, 365 insertions(+), 2 deletions(-) diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index b0310754b5..0936f083cd 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -16,7 +16,8 @@ import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart'; import 'package:PiliPlus/pages/live_room/superchat/superchat_panel.dart'; import 'package:PiliPlus/pages/live_room/widgets/bottom_control.dart'; import 'package:PiliPlus/pages/live_room/widgets/chat_panel.dart'; -import 'package:PiliPlus/pages/live_room/widgets/header_control.dart'; +import 'package:PiliPlus/pages/live_room/widgets/header_control.dart' + show LiveHeaderControl, LiveHeaderControlState; import 'package:PiliPlus/pages/video/widgets/player_focus.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; @@ -199,6 +200,12 @@ class _LiveRoomPageState extends State child = PlayerFocus( plPlayerController: plPlayerController, onSendDanmaku: _liveRoomController.onSendDanmaku, + onShowMenu: () { + final headerState = _liveRoomController.headerKey.currentState; + if (headerState is LiveHeaderControlState) { + headerState.showSettingSheet(); + } + }, child: child, ); } diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index f311968444..716dd6835a 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -38,7 +38,7 @@ class LiveHeaderControl extends StatefulWidget { } class _LiveHeaderControlState extends State - with TimeBatteryMixin { + with TimeBatteryMixin, HeaderMixin { @override late final plPlayerController = widget.plPlayerController; @@ -51,6 +51,349 @@ class _LiveHeaderControlState extends State @override bool get isPortrait => widget.isPortrait; + static const TextStyle subTitleStyle = TextStyle(fontSize: 12); + static const TextStyle titleStyle = TextStyle(fontSize: 14); + + /// 设置面板 + void showSettingSheet() { + final liveController = widget.liveController; + showBottomSheet( + (context, setState) { + final theme = Theme.of(context); + return Padding( + padding: const EdgeInsets.all(12), + child: Material( + clipBehavior: Clip.hardEdge, + color: theme.colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 14), + children: [ + ListTile( + dense: true, + onTap: () { + Get.back(); + widget.onSendDanmaku(); + }, + leading: const Icon(Icons.comment_outlined, size: 20), + title: const Text('发弹幕', style: titleStyle), + ), + Obx( + () { + final onlyPlayAudio = plPlayerController.onlyPlayAudio.value; + return ListTile( + dense: true, + onTap: () { + plPlayerController.onlyPlayAudio.value = !onlyPlayAudio; + widget.onPlayAudio(); + setState(() {}); + }, + leading: onlyPlayAudio + ? const Icon(MdiIcons.musicCircle, size: 20) + : const Icon(MdiIcons.musicCircleOutline, size: 20), + title: const Text('仅播放音频', style: titleStyle), + trailing: onlyPlayAudio + ? Icon( + Icons.done, + color: theme.colorScheme.primary, + ) + : null, + ); + }, + ), + if (Platform.isAndroid || (Utils.isDesktop && !isFullScreen)) + ListTile( + dense: true, + onTap: () async { + Get.back(); + if (Utils.isDesktop) { + plPlayerController.toggleDesktopPip(); + return; + } + if (await Floating().isPipAvailable) { + plPlayerController + ..showControls.value = false + ..enterPip(); + } + }, + leading: const Icon(Icons.picture_in_picture_outlined, size: 20), + title: const Text('画中画', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + PageUtils.scheduleExit(context, isFullScreen, true); + }, + leading: const Icon(Icons.schedule, size: 20), + title: const Text('定时关闭', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + HeaderControlState.showPlayerInfo( + context, + plPlayerController: plPlayerController, + ); + }, + leading: const Icon(Icons.info_outline, size: 20), + title: const Text('播放信息', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + liveController.queryLiveUrl(); + }, + leading: const Icon(Icons.refresh, size: 20), + title: const Text('刷新', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + if (liveController.isLogin) { + Get.toNamed( + '/liveDmBlockPage', + parameters: { + 'roomId': liveController.roomId.toString(), + }, + ); + } else { + SmartDialog.showToast('账号未登录'); + } + }, + leading: const Icon(Icons.block, size: 20), + title: const Text('屏蔽', style: titleStyle), + ), + SwitchListTile( + value: plPlayerController.enableShowDanmaku.value, + onChanged: (value) { + setState(() { + plPlayerController.enableShowDanmaku.value = value; + if (!plPlayerController.tempPlayerConf) { + GStorage.setting.put( + SettingBoxKey.enableShowLiveDanmaku, + value, + ); + } + }); + }, + secondary: const Icon(Icons.comment_outlined, size: 20), + title: const Text('弹幕', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + showSetDanmaku(isLive: true); + }, + leading: const Icon(CustomIcons.dm_settings, size: 20), + title: const Text('弹幕设置', style: titleStyle), + ), + ListTile( + dense: true, + leading: const Icon(Icons.aspect_ratio_outlined, size: 20), + title: Row( + children: [ + const Text( + '画面比例', + strutStyle: StrutStyle(leading: 0, height: 1), + style: TextStyle( + height: 1, + fontSize: 14, + ), + ), + const SizedBox(width: 10), + Obx( + () => PopupMenuButton( + initialValue: plPlayerController.videoFit.value, + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + plPlayerController.videoFit.value.desc, + strutStyle: const StrutStyle( + leading: 0, + height: 1, + ), + style: TextStyle( + height: 1, + fontSize: 14, + color: theme.colorScheme.secondary, + ), + ), + Icon( + MdiIcons.unfoldMoreHorizontal, + size: MediaQuery.textScalerOf( + context, + ).scale(14), + color: theme.colorScheme.secondary, + ), + ], + ), + ), + onSelected: (value) { + plPlayerController.toggleVideoFit(value); + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + }, + itemBuilder: (context) => VideoFitType.values + .map( + (item) => PopupMenuItem( + value: item, + child: Text(item.desc), + ), + ) + .toList(), + ), + ), + ], + ), + ), + ListTile( + dense: true, + leading: const Icon(Icons.play_circle_outline, size: 20), + title: Row( + children: [ + const Text( + '画质', + strutStyle: StrutStyle(leading: 0, height: 1), + style: TextStyle( + height: 1, + fontSize: 14, + ), + ), + const SizedBox(width: 10), + Obx( + () => PopupMenuButton( + initialValue: liveController.currentQn, + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + liveController.currentQnDesc.value, + strutStyle: const StrutStyle( + leading: 0, + height: 1, + ), + style: TextStyle( + height: 1, + fontSize: 14, + color: theme.colorScheme.secondary, + ), + ), + Icon( + MdiIcons.unfoldMoreHorizontal, + size: MediaQuery.textScalerOf( + context, + ).scale(14), + color: theme.colorScheme.secondary, + ), + ], + ), + ), + onSelected: (value) { + liveController.changeQn(value); + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + }, + itemBuilder: (context) => liveController.acceptQnList + .map( + (item) => PopupMenuItem( + value: item.code, + child: Text(item.desc), + ), + ) + .toList(), + ), + ), + ], + ), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + spacing: 10, + children: [ + Obx( + () { + final flipX = plPlayerController.flipX.value; + return ActionRowLineItem( + iconData: Icons.flip, + onTap: () => + plPlayerController.flipX.value = !flipX, + text: " 左右翻转 ", + selectStatus: flipX, + ); + }, + ), + Obx( + () { + final flipY = plPlayerController.flipY.value; + return ActionRowLineItem( + icon: Transform.rotate( + angle: 1.5707963267948966, // pi / 2 + child: Icon( + Icons.flip, + size: 13, + color: flipY + ? theme.colorScheme.onSecondaryContainer + : theme.colorScheme.outline, + ), + ), + onTap: () { + plPlayerController.flipY.value = !flipY; + }, + text: " 上下翻转 ", + selectStatus: flipY, + ); + }, + ), + Obx( + () => ActionRowLineItem( + iconData: Icons.play_circle_outline, + onTap: plPlayerController.setContinuePlayInBackground, + text: " 后台播放 ", + selectStatus: + plPlayerController.continuePlayInBackground.value, + ), + ), + ], + ), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + }, + leading: const Icon(Icons.arrow_back, size: 20), + title: const Text('返回', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () { + Get.until((route) => route.isFirst); + }, + leading: const Icon(Icons.home, size: 20), + title: const Text('返回主页', style: titleStyle), + ), + ], + ), + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { final isFullScreen = this.isFullScreen; @@ -199,8 +542,21 @@ class _LiveHeaderControlState extends State color: Colors.white, ), ), + ComBtn( + height: 30, + tooltip: "更多设置", + icon: const Icon( + Icons.more_vert_outlined, + size: 19, + color: Colors.white, + ), + onTap: showSettingSheet, + ), ], ), ); } } + +// Export the private state class for access via GlobalKey +typedef LiveHeaderControlState = _LiveHeaderControlState; From fae4fa5c87c2f638bcf7aaa01b03d4d246bfb759 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:13:56 +0000 Subject: [PATCH 3/5] Fix imports for live room header control Co-authored-by: UcnacDx2 <127503808+UcnacDx2@users.noreply.github.com> --- lib/pages/live_room/widgets/header_control.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index 716dd6835a..3f1951b3ac 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -1,14 +1,21 @@ import 'dart:io'; +import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/marquee.dart'; import 'package:PiliPlus/pages/live_room/controller.dart'; -import 'package:PiliPlus/pages/video/widgets/header_control.dart'; +import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart'; +import 'package:PiliPlus/pages/video/widgets/header_control.dart' + show HeaderMixin, HeaderControlState, TimeBatteryMixin; import 'package:PiliPlus/plugin/pl_player/controller.dart'; +import 'package:PiliPlus/plugin/pl_player/models/video_fit_type.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart'; import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; From 88fa0191fe4ef8985ba65cbf530edb25f2208afa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:14:51 +0000 Subject: [PATCH 4/5] Fix ActionRowLineItem import Co-authored-by: UcnacDx2 <127503808+UcnacDx2@users.noreply.github.com> --- lib/pages/live_room/widgets/header_control.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index 3f1951b3ac..f15886c311 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/marquee.dart'; import 'package:PiliPlus/pages/live_room/controller.dart'; -import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart'; +import 'package:PiliPlus/pages/video/introduction/ugc/widgets/menu_row.dart'; import 'package:PiliPlus/pages/video/widgets/header_control.dart' show HeaderMixin, HeaderControlState, TimeBatteryMixin; import 'package:PiliPlus/plugin/pl_player/controller.dart'; From bc04822228ed3244b68950d3a09a73236a0d9baf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:17:13 +0000 Subject: [PATCH 5/5] Address code review feedback: fix GlobalKey type and magic number Co-authored-by: UcnacDx2 <127503808+UcnacDx2@users.noreply.github.com> --- lib/pages/live_room/controller.dart | 3 ++- lib/pages/live_room/widgets/header_control.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index c1df7807c5..3394450cd0 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -14,6 +14,7 @@ import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/danmaku/danmaku_model.dart'; import 'package:PiliPlus/pages/live_room/send_danmaku/view.dart'; +import 'package:PiliPlus/pages/live_room/widgets/header_control.dart'; import 'package:PiliPlus/pages/video/widgets/header_control.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; @@ -113,7 +114,7 @@ class LiveRoomController extends GetxController { final showSuperChat = Pref.showSuperChat; - final headerKey = GlobalKey(); + final headerKey = GlobalKey(); final RxString title = ''.obs; diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index f15886c311..5dc824be8c 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:math' as math; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/marquee.dart'; @@ -348,7 +349,7 @@ class _LiveHeaderControlState extends State final flipY = plPlayerController.flipY.value; return ActionRowLineItem( icon: Transform.rotate( - angle: 1.5707963267948966, // pi / 2 + angle: math.pi / 2, child: Icon( Icons.flip, size: 13,