From 14129e8f21c58118b246ffa81abaa611b5ce3629 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 23 Feb 2025 16:59:46 +0800 Subject: [PATCH] mod: horizontal live room Closes #62 Signed-off-by: bggRGjQaUbCoE --- lib/pages/live_room/view.dart | 523 ++++++++++-------- .../video/detail/reply_new/reply_page.dart | 2 +- lib/plugin/pl_player/view.dart | 3 + 3 files changed, 306 insertions(+), 222 deletions(-) diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 4cfe88ce..cb63c566 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/pages/live_room/widgets/chat.dart'; @@ -73,11 +74,11 @@ class _LiveRoomPageState extends State _updateFontSize(); } }); - WidgetsBinding.instance.addPostFrameCallback((_) { - if (context.orientation == Orientation.landscape) { - plPlayerController.triggerFullScreen(status: true); - } - }); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // if (context.orientation == Orientation.landscape) { + // plPlayerController.triggerFullScreen(status: true); + // } + // }); } void _updateFontSize() async { @@ -130,12 +131,18 @@ class _LiveRoomPageState extends State } } - Widget get videoPlayerPanel { + final GlobalKey videoPlayerKey = GlobalKey(); + final GlobalKey playerKey = GlobalKey(); + + Widget videoPlayerPanel([Color? fill]) { return FutureBuilder( + key: videoPlayerKey, future: _futureBuilderFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData && snapshot.data['status']) { return PLVideoPlayer( + key: playerKey, + fill: fill, plPlayerController: plPlayerController, bottomControl: BottomControl( controller: plPlayerController, @@ -176,11 +183,10 @@ class _LiveRoomPageState extends State ); } - Widget get childWhenDisabled { - return Scaffold( - primary: true, - backgroundColor: Colors.black, - body: Stack( + Widget childWhenDisabled(bool isPortrait) { + return ColoredBox( + color: Colors.black, + child: Stack( children: [ Positioned( left: 0, @@ -221,211 +227,22 @@ class _LiveRoomPageState extends State : const SizedBox(), ), ), - Column( - children: [ - AppBar( - backgroundColor: Colors.transparent, - foregroundColor: Colors.white, - titleTextStyle: TextStyle(color: Colors.white), - toolbarHeight: - MediaQuery.of(context).orientation == Orientation.portrait - ? 56 - : 0, - title: FutureBuilder( - future: _futureBuilder, - builder: (context, snapshot) { - if (snapshot.data == null) { - return const SizedBox(); - } - Map data = snapshot.data as Map; - if (data['status']) { - return Obx( - () => Row( - children: [ - GestureDetector( - onTap: () { - _node.unfocus(); - dynamic uid = _liveRoomController - .roomInfoH5.value.roomInfo?.uid; - Get.toNamed( - '/member?mid=$uid', - arguments: { - 'heroTag': Utils.makeHeroTag(uid), - }, - ); - }, - child: NetworkImgLayer( - width: 34, - height: 34, - type: 'avatar', - src: _liveRoomController.roomInfoH5.value - .anchorInfo!.baseInfo!.face, - ), - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _liveRoomController.roomInfoH5.value - .anchorInfo!.baseInfo!.uname!, - style: const TextStyle(fontSize: 14), - ), - const SizedBox(height: 1), - if (_liveRoomController - .roomInfoH5.value.watchedShow != - null) - Text( - _liveRoomController.roomInfoH5.value - .watchedShow!['text_large'] ?? - '', - style: const TextStyle(fontSize: 12), - ), - ], - ), - const Spacer(), - //刷新 - IconButton( - tooltip: '刷新', - onPressed: () { - _futureBuilderFuture = - _liveRoomController.queryLiveInfo(); - // videoSourceInit(); - }, - icon: const Icon(Icons.refresh), - ), - //内置浏览器打开 - IconButton( - tooltip: '浏览器打开', - onPressed: () { - Utils.inAppWebview( - 'https://live.bilibili.com/h5/${_liveRoomController.roomId}', - off: true, - ); - }, - icon: const Icon(Icons.open_in_browser)), - ], - ), - ); - } else { - return const SizedBox(); - } - }, - ), - ), - Obx( - () => PopScope( - canPop: plPlayerController.isFullScreen.value != true, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (plPlayerController.isFullScreen.value == true) { - plPlayerController.triggerFullScreen(status: false); - // if (MediaQuery.of(context).orientation == - // Orientation.landscape) { - // verticalScreenForTwoSeconds(); - // } - } - }, - child: Listener( - onPointerDown: (_) { - _node.unfocus(); - }, - child: SizedBox( - width: Get.size.width, - height: MediaQuery.of(context).orientation == - Orientation.landscape - ? Get.size.height - : Get.size.width * 9 / 16, - child: videoPlayerPanel, - ), + isPortrait + ? Scaffold( + backgroundColor: Colors.transparent, + body: Column( + children: [ + _buildAppBar, + ..._buildBodyP, + ], ), - ), - ), - Expanded( - child: Listener( - onPointerDown: (_) { - _node.unfocus(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: LiveRoomChat( - roomId: _roomId, - liveRoomController: _liveRoomController, - ), - ), - ), - ), - Container( - padding: EdgeInsets.only( - left: 10, - top: 10, - right: 10, - bottom: 25 + MediaQuery.of(context).padding.bottom, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - border: Border( - top: BorderSide(color: Color(0x1AFFFFFF)), - ), - color: Color(0x1AFFFFFF), - ), - child: Row( + ) + : Column( children: [ - Obx( - () => IconButton( - onPressed: () { - plPlayerController.isOpenDanmu.value = - !plPlayerController.isOpenDanmu.value; - GStorage.setting.put(SettingBoxKey.enableShowDanmaku, - plPlayerController.isOpenDanmu.value); - }, - icon: Icon( - plPlayerController.isOpenDanmu.value - ? Icons.subtitles_outlined - : Icons.subtitles_off_outlined, - color: _color, - ), - ), - ), - Expanded( - child: TextField( - focusNode: _node, - controller: _ctr, - textInputAction: TextInputAction.send, - cursorColor: _color, - style: TextStyle(color: _color), - onSubmitted: (value) { - if (value.isNotEmpty) { - _onSendMsg(value); - } - }, - decoration: InputDecoration( - border: InputBorder.none, - hintText: '发送弹幕', - hintStyle: TextStyle( - color: Colors.white.withOpacity(0.6), - ), - ), - ), - ), - IconButton( - onPressed: () { - if (_ctr.text.isNotEmpty) { - _onSendMsg(_ctr.text); - } - }, - icon: Icon( - Icons.send, - color: _color, - ), - ), + _buildAppBar, + _buildBodyH, ], ), - ), - ], - ), ], ), ); @@ -436,19 +253,280 @@ class _LiveRoomPageState extends State WidgetsBinding.instance.addPostFrameCallback((_) { _updateFontSize(); }); - if (Platform.isAndroid) { - return PiPSwitcher( - getChildWhenDisabled: () => childWhenDisabled, - getChildWhenEnabled: () => videoPlayerPanel, - floating: floating, - ); - } else { - return childWhenDisabled; - } + return OrientationBuilder( + builder: (BuildContext context, Orientation orientation) { + if (Platform.isAndroid) { + return PiPSwitcher( + getChildWhenDisabled: () => + childWhenDisabled(orientation == Orientation.portrait), + getChildWhenEnabled: () => videoPlayerPanel, + floating: floating, + ); + } else { + return childWhenDisabled(orientation == Orientation.portrait); + } + }, + ); } Color get _color => Color(0xFFEEEEEE); + Widget get _buildAppBar => AppBar( + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + titleTextStyle: TextStyle(color: Colors.white), + toolbarHeight: + MediaQuery.of(context).orientation == Orientation.portrait ? 56 : 0, + title: FutureBuilder( + future: _futureBuilder, + builder: (context, snapshot) { + if (snapshot.data == null) { + return const SizedBox(); + } + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () => Row( + children: [ + GestureDetector( + onTap: () { + _node.unfocus(); + dynamic uid = + _liveRoomController.roomInfoH5.value.roomInfo?.uid; + Get.toNamed( + '/member?mid=$uid', + arguments: { + 'heroTag': Utils.makeHeroTag(uid), + }, + ); + }, + child: NetworkImgLayer( + width: 34, + height: 34, + type: 'avatar', + src: _liveRoomController + .roomInfoH5.value.anchorInfo!.baseInfo!.face, + ), + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _liveRoomController + .roomInfoH5.value.anchorInfo!.baseInfo!.uname!, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 1), + if (_liveRoomController.roomInfoH5.value.watchedShow != + null) + Text( + _liveRoomController.roomInfoH5.value + .watchedShow!['text_large'] ?? + '', + style: const TextStyle(fontSize: 12), + ), + ], + ), + const Spacer(), + //刷新 + IconButton( + tooltip: '刷新', + onPressed: () { + _futureBuilderFuture = + _liveRoomController.queryLiveInfo(); + // videoSourceInit(); + }, + icon: const Icon(Icons.refresh), + ), + //内置浏览器打开 + IconButton( + tooltip: '浏览器打开', + onPressed: () { + Utils.inAppWebview( + 'https://live.bilibili.com/h5/${_liveRoomController.roomId}', + off: true, + ); + }, + icon: const Icon(Icons.open_in_browser)), + ], + ), + ); + } else { + return const SizedBox(); + } + }, + ), + ); + + Widget get _buildBodyH { + double videoWidth = + max(context.height / context.width * 1.04, 0.58) * context.width; + return Expanded( + child: Row( + children: [ + Obx( + () => PopScope( + canPop: plPlayerController.isFullScreen.value != true, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (plPlayerController.isFullScreen.value == true) { + plPlayerController.triggerFullScreen(status: false); + } + }, + child: Listener( + onPointerDown: (_) { + _node.unfocus(); + }, + child: SizedBox( + width: plPlayerController.isFullScreen.value + ? Get.size.width + : videoWidth, + height: plPlayerController.isFullScreen.value + ? Get.size.height + : Get.size.width * 9 / 16, + child: MediaQuery.removePadding( + removeRight: true, + context: context, + child: videoPlayerPanel(Colors.transparent), + ), + ), + ), + ), + ), + Expanded( + child: Scaffold( + backgroundColor: Colors.transparent, + body: SafeArea( + left: false, + top: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildBottomWidget, + ), + ), + ), + ), + ], + ), + ); + } + + List get _buildBodyP => [ + Obx( + () => PopScope( + canPop: plPlayerController.isFullScreen.value != true, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (plPlayerController.isFullScreen.value == true) { + plPlayerController.triggerFullScreen(status: false); + } + }, + child: Listener( + onPointerDown: (_) { + _node.unfocus(); + }, + child: SizedBox( + width: Get.size.width, + height: plPlayerController.isFullScreen.value + ? Get.size.height + : Get.size.width * 9 / 16, + child: videoPlayerPanel(), + ), + ), + ), + ), + ..._buildBottomWidget, + ]; + + final GlobalKey chatKey = GlobalKey(); + + List get _buildBottomWidget => [ + Expanded( + child: Listener( + onPointerDown: (_) { + _node.unfocus(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: LiveRoomChat( + key: chatKey, + roomId: _roomId, + liveRoomController: _liveRoomController, + ), + ), + ), + ), + Container( + padding: EdgeInsets.only( + left: 10, + top: 10, + right: 10, + bottom: 25 + MediaQuery.of(context).padding.bottom, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + border: Border( + top: BorderSide(color: Color(0x1AFFFFFF)), + ), + color: Color(0x1AFFFFFF), + ), + child: Row( + children: [ + Obx( + () => IconButton( + onPressed: () { + plPlayerController.isOpenDanmu.value = + !plPlayerController.isOpenDanmu.value; + GStorage.setting.put(SettingBoxKey.enableShowDanmaku, + plPlayerController.isOpenDanmu.value); + }, + icon: Icon( + plPlayerController.isOpenDanmu.value + ? Icons.subtitles_outlined + : Icons.subtitles_off_outlined, + color: _color, + ), + ), + ), + Expanded( + child: TextField( + focusNode: _node, + controller: _ctr, + textInputAction: TextInputAction.send, + cursorColor: _color, + style: TextStyle(color: _color), + onSubmitted: (value) { + if (value.isNotEmpty) { + _onSendMsg(value); + } + }, + decoration: InputDecoration( + border: InputBorder.none, + hintText: '发送弹幕', + hintStyle: TextStyle( + color: Colors.white.withOpacity(0.6), + ), + ), + ), + ), + IconButton( + onPressed: () { + if (_ctr.text.isNotEmpty) { + _onSendMsg(_ctr.text); + } + }, + icon: Icon( + Icons.send, + color: _color, + ), + ), + ], + ), + ) + ]; + void _onSendMsg(msg) async { if (!_isLogin) { SmartDialog.showToast('未登录'); @@ -458,6 +536,9 @@ class _LiveRoomPageState extends State roomId: _liveRoomController.roomId, msg: msg); if (res['status']) { _ctr.clear(); + if (mounted) { + FocusScope.of(context).unfocus(); + } SmartDialog.showToast('发送成功'); } else { SmartDialog.showToast(res['msg']); diff --git a/lib/pages/video/detail/reply_new/reply_page.dart b/lib/pages/video/detail/reply_new/reply_page.dart index ceeafcf9..48109cbf 100644 --- a/lib/pages/video/detail/reply_new/reply_page.dart +++ b/lib/pages/video/detail/reply_new/reply_page.dart @@ -34,7 +34,7 @@ class ReplyPage extends CommonPublishPage { } class _ReplyPageState extends CommonPublishPageState { - RxBool _syncToDynamic = false.obs; + final RxBool _syncToDynamic = false.obs; @override Widget build(BuildContext context) { diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 7fb4b9e9..2adb6f47 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -53,6 +53,7 @@ class PLVideoPlayer extends StatefulWidget { this.customWidgets, this.showEpisodes, this.showViewPoints, + this.fill, super.key, }); @@ -68,6 +69,7 @@ class PLVideoPlayer extends StatefulWidget { final List? customWidgets; final Function? showEpisodes; final VoidCallback? showViewPoints; + final Color? fill; @override State createState() => _PLVideoPlayerState(); @@ -844,6 +846,7 @@ class _PLVideoPlayerState extends State flipX: plPlayerController.flipX.value, flipY: plPlayerController.flipY.value, child: Video( + fill: widget.fill ?? Colors.black, key: plPlayerController.key, controller: videoController, controls: NoVideoControls,