From ab10223eca6e0936ad6835dab86cdc6ffbef47b2 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 11 Mar 2024 23:03:50 +0800 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20=E6=8E=A5=E6=94=B6=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=BB=84=E4=BB=B6=E4=BC=A0=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pl_player/models/bottom_control_type.dart | 1 + lib/plugin/pl_player/view.dart | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/plugin/pl_player/models/bottom_control_type.dart b/lib/plugin/pl_player/models/bottom_control_type.dart index 599f6e4f..739e1d38 100644 --- a/lib/plugin/pl_player/models/bottom_control_type.dart +++ b/lib/plugin/pl_player/models/bottom_control_type.dart @@ -7,4 +7,5 @@ enum BottomControlType { fit, speed, fullscreen, + custom, } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 0b2e652e..1f3c4156 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -34,6 +34,8 @@ class PLVideoPlayer extends StatefulWidget { this.bottomControl, this.danmuWidget, this.bottomList, + this.customWidget, + this.customWidgets, super.key, }); @@ -42,6 +44,10 @@ class PLVideoPlayer extends StatefulWidget { final PreferredSizeWidget? bottomControl; final Widget? danmuWidget; final List? bottomList; + // List or Widget + + final Widget? customWidget; + final List? customWidgets; @override State createState() => _PLVideoPlayerState(); @@ -310,7 +316,7 @@ class _PLVideoPlayerState extends State ), }; final List list = []; - var userSpecifyItem = widget.bottomList ?? + List userSpecifyItem = widget.bottomList ?? [ BottomControlType.playOrPause, BottomControlType.time, @@ -319,7 +325,16 @@ class _PLVideoPlayerState extends State BottomControlType.fullscreen, ]; for (var i = 0; i < userSpecifyItem.length; i++) { - list.add(videoProgressWidgets[userSpecifyItem[i]]!); + if (userSpecifyItem[i] == BottomControlType.custom) { + if (widget.customWidget != null && widget.customWidget is Widget) { + list.add(widget.customWidget!); + } + if (widget.customWidgets != null && widget.customWidgets!.isNotEmpty) { + list.addAll(widget.customWidgets!); + } + } else { + list.add(videoProgressWidgets[userSpecifyItem[i]]!); + } } return list; } From ed8443ba02a2660d5e33e77eee314dcfad98a0b6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 27 Mar 2024 22:26:47 +0800 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20navigation=20Bar=E7=BC=96?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/nav_bar_config.dart | 9 ++ lib/pages/main/controller.dart | 38 ++++--- .../setting/pages/navigation_bar_set.dart | 100 ++++++++++++++++++ lib/pages/setting/style_setting.dart | 7 +- lib/router/app_pages.dart | 4 + lib/utils/storage.dart | 3 +- 6 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 lib/pages/setting/pages/navigation_bar_set.dart diff --git a/lib/models/common/nav_bar_config.dart b/lib/models/common/nav_bar_config.dart index 9ebe8e6f..64cebafb 100644 --- a/lib/models/common/nav_bar_config.dart +++ b/lib/models/common/nav_bar_config.dart @@ -1,5 +1,10 @@ import 'package:flutter/material.dart'; +import '../../pages/dynamics/index.dart'; +import '../../pages/home/index.dart'; +import '../../pages/media/index.dart'; +import '../../pages/rank/index.dart'; + List defaultNavigationBars = [ { 'id': 0, @@ -13,6 +18,7 @@ List defaultNavigationBars = [ ), 'label': "首页", 'count': 0, + 'page': const HomePage(), }, { 'id': 1, @@ -26,6 +32,7 @@ List defaultNavigationBars = [ ), 'label': "排行榜", 'count': 0, + 'page': const RankPage(), }, { 'id': 2, @@ -39,6 +46,7 @@ List defaultNavigationBars = [ ), 'label': "动态", 'count': 0, + 'page': const DynamicsPage(), }, { 'id': 3, @@ -52,5 +60,6 @@ List defaultNavigationBars = [ ), 'label': "媒体库", 'count': 0, + 'page': const MediaPage(), } ]; diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index ddbd364a..f929a1aa 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -6,23 +6,16 @@ import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/common.dart'; -import 'package:pilipala/pages/dynamics/index.dart'; -import 'package:pilipala/pages/home/view.dart'; -import 'package:pilipala/pages/media/index.dart'; -import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { - List pages = [ - const HomePage(), - const RankPage(), - const DynamicsPage(), - const MediaPage(), - ]; - RxList navigationBars = defaultNavigationBars.obs; + List pages = []; + RxList navigationBars = [].obs; + late List defaultNavTabs; + late List navBarSort; final StreamController bottomBarStream = StreamController.broadcast(); Box setting = GStrorage.setting; @@ -41,10 +34,7 @@ class MainController extends GetxController { Utils.checkUpdata(); } hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true); - int defaultHomePage = - setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int; - selectedIndex = defaultNavigationBars - .indexWhere((item) => item['id'] == defaultHomePage); + var userInfo = userInfoCache.get('userInfoCache'); userLogin.value = userInfo != null; dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( @@ -53,6 +43,7 @@ class MainController extends GetxController { if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { getUnreadDynamic(); } + setNavBarConfig(); } void onBackPressed(BuildContext context) { @@ -93,4 +84,21 @@ class MainController extends GetxController { } navigationBars.refresh(); } + + void setNavBarConfig() async { + defaultNavTabs = [...defaultNavigationBars]; + navBarSort = + setting.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]); + defaultNavTabs.retainWhere((item) => navBarSort.contains(item['id'])); + defaultNavTabs.sort((a, b) => + navBarSort.indexOf(a['id']).compareTo(navBarSort.indexOf(b['id']))); + navigationBars.value = defaultNavTabs; + int defaultHomePage = + setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int; + int defaultIndex = + navigationBars.indexWhere((item) => item['id'] == defaultHomePage); + // 如果找不到匹配项,默认索引设置为0或其他合适的值 + selectedIndex = defaultIndex != -1 ? defaultIndex : 0; + pages = navigationBars.map((e) => e['page']).toList(); + } } diff --git a/lib/pages/setting/pages/navigation_bar_set.dart b/lib/pages/setting/pages/navigation_bar_set.dart new file mode 100644 index 00000000..8e1771e3 --- /dev/null +++ b/lib/pages/setting/pages/navigation_bar_set.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/models/common/tab_type.dart'; +import 'package:pilipala/utils/storage.dart'; + +import '../../../models/common/nav_bar_config.dart'; + +class NavigationBarSetPage extends StatefulWidget { + const NavigationBarSetPage({super.key}); + + @override + State createState() => _NavigationbarSetPageState(); +} + +class _NavigationbarSetPageState extends State { + Box settingStorage = GStrorage.setting; + late List defaultNavTabs; + late List navBarSort; + + @override + void initState() { + super.initState(); + defaultNavTabs = defaultNavigationBars; + navBarSort = settingStorage + .get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]); + // 对 tabData 进行排序 + defaultNavTabs.sort((a, b) { + int indexA = navBarSort.indexOf(a['id']); + int indexB = navBarSort.indexOf(b['id']); + + // 如果类型在 sortOrder 中不存在,则放在末尾 + if (indexA == -1) indexA = navBarSort.length; + if (indexB == -1) indexB = navBarSort.length; + + return indexA.compareTo(indexB); + }); + } + + void saveEdit() { + List sortedTabbar = defaultNavTabs + .where((i) => navBarSort.contains(i['id'])) + .map((i) => i['id']) + .toList(); + settingStorage.put(SettingBoxKey.navBarSort, sortedTabbar); + SmartDialog.showToast('保存成功,下次启动时生效'); + } + + void onReorder(int oldIndex, int newIndex) { + setState(() { + if (newIndex > oldIndex) { + newIndex -= 1; + } + final tabsItem = defaultNavTabs.removeAt(oldIndex); + defaultNavTabs.insert(newIndex, tabsItem); + }); + } + + @override + Widget build(BuildContext context) { + final listTiles = [ + for (int i = 0; i < defaultNavTabs.length; i++) ...[ + CheckboxListTile( + key: Key(defaultNavTabs[i]['label']), + value: navBarSort.contains(defaultNavTabs[i]['id']), + onChanged: (bool? newValue) { + int tabTypeId = defaultNavTabs[i]['id']; + if (!newValue!) { + navBarSort.remove(tabTypeId); + } else { + navBarSort.add(tabTypeId); + } + setState(() {}); + }, + title: Text(defaultNavTabs[i]['label']), + secondary: const Icon(Icons.drag_indicator_rounded), + enabled: defaultNavTabs[i]['id'] != 3, + ) + ] + ]; + + return Scaffold( + appBar: AppBar( + title: const Text('Navbar编辑'), + actions: [ + TextButton(onPressed: () => saveEdit(), child: const Text('保存')), + const SizedBox(width: 12) + ], + ), + body: ReorderableListView( + onReorder: onReorder, + physics: const NeverScrollableScrollPhysics(), + footer: SizedBox( + height: MediaQuery.of(context).padding.bottom + 30, + ), + children: listTiles, + ), + ); + } +} diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 30b9a30f..d2403cff 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -284,12 +284,17 @@ class _StyleSettingState extends State { onTap: () => Get.toNamed('/tabbarSetting'), title: Text('首页tabbar', style: titleStyle), ), + ListTile( + dense: false, + onTap: () => Get.toNamed('/navbarSetting'), + title: Text('navbar设置', style: titleStyle), + ), if (Platform.isAndroid) ListTile( dense: false, onTap: () => Get.toNamed('/displayModeSetting'), title: Text('屏幕帧率', style: titleStyle), - ) + ), ], ), ); diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 1f1ea31e..7fda1bd8 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -39,6 +39,7 @@ import '../pages/setting/pages/color_select.dart'; import '../pages/setting/pages/display_mode.dart'; import '../pages/setting/pages/font_size_select.dart'; import '../pages/setting/pages/home_tabbar_set.dart'; +import '../pages/setting/pages/navigation_bar_set.dart'; import '../pages/setting/pages/play_gesture_set.dart'; import '../pages/setting/pages/play_speed_set.dart'; import '../pages/setting/recommend_setting.dart'; @@ -170,6 +171,9 @@ class Routes { // 播放器手势 CustomGetPage( name: '/playerGestureSet', page: () => const PlayGesturePage()), + // navigation bar + CustomGetPage( + name: '/navbarSetting', page: () => const NavigationBarSetPage()), ]; } diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index a82972e0..29cf1846 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -148,7 +148,8 @@ class SettingBoxKey { hideTabBar = 'hideTabBar', // 收起底栏 tabbarSort = 'tabbarSort', // 首页tabbar dynamicBadgeMode = 'dynamicBadgeMode', - enableGradientBg = 'enableGradientBg'; + enableGradientBg = 'enableGradientBg', + navBarSort = 'navBarSort'; } class LocalCacheKey { From c0f3b4f3a2052eb6b4aa4f73022f6f884d432d3e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 2 Apr 2024 23:13:15 +0800 Subject: [PATCH 03/18] =?UTF-8?q?fix:=20=E5=85=A8=E5=B1=8F=E6=97=B6?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E5=BC=B9=E5=B9=95=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/widgets/header_control.dart | 132 ++++++++++++++++-- 1 file changed, 123 insertions(+), 9 deletions(-) diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 858ca2df..9448a62a 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -17,6 +17,7 @@ import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/services/shutdown_timer_service.dart'; +import '../../../../http/danmaku.dart'; import '../../../../models/common/search_type.dart'; import '../../../../models/video_detail_res.dart'; import '../introduction/index.dart'; @@ -52,7 +53,7 @@ class _HeaderControlState extends State { final Box videoStorage = GStrorage.video; late List speedsList; double buttonSpace = 8; - bool showTitle = false; + RxBool isFullScreen = false.obs; late String heroTag; late VideoIntroController videoIntroController; late VideoDetailData videoDetail; @@ -69,13 +70,8 @@ class _HeaderControlState extends State { } void fullScreenStatusListener() { - widget.videoDetailCtr!.plPlayerController.isFullScreen - .listen((bool isFullScreen) { - if (isFullScreen) { - showTitle = true; - } else { - showTitle = false; - } + widget.videoDetailCtr!.plPlayerController.isFullScreen.listen((bool val) { + isFullScreen.value = val; /// TODO setState() called after dispose() if (mounted) { @@ -218,6 +214,87 @@ class _HeaderControlState extends State { ); } + /// 发送弹幕 + void showShootDanmakuSheet() { + final TextEditingController textController = TextEditingController(); + bool isSending = false; // 追踪是否正在发送 + showDialog( + context: Get.context!, + builder: (BuildContext context) { + // TODO: 支持更多类型和颜色的弹幕 + return AlertDialog( + title: const Text('发送弹幕(测试)'), + content: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return TextField( + controller: textController, + ); + }), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return TextButton( + onPressed: isSending + ? null + : () async { + final String msg = textController.text; + if (msg.isEmpty) { + SmartDialog.showToast('弹幕内容不能为空'); + return; + } else if (msg.length > 100) { + SmartDialog.showToast('弹幕内容不能超过100个字符'); + return; + } + setState(() { + isSending = true; // 开始发送,更新状态 + }); + //修改按钮文字 + final dynamic res = await DanmakaHttp.shootDanmaku( + oid: widget.videoDetailCtr!.cid.value, + msg: textController.text, + bvid: widget.videoDetailCtr!.bvid, + progress: + widget.controller!.position.value.inMilliseconds, + type: 1, + ); + setState(() { + isSending = false; // 发送结束,更新状态 + }); + if (res['status']) { + SmartDialog.showToast('发送成功'); + // 发送成功,自动预览该弹幕,避免重新请求 + // TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现 + widget.controller!.danmakuController!.addItems([ + DanmakuItem( + msg, + color: Colors.white, + time: widget + .controller!.position.value.inMilliseconds, + type: DanmakuItemType.scroll, + isSend: true, + ) + ]); + Get.back(); + } else { + SmartDialog.showToast('发送失败,错误信息为${res['msg']}'); + } + }, + child: Text(isSending ? '发送中...' : '发送'), + ); + }) + ], + ); + }, + ); + } + /// 定时关闭 void scheduleExit() async { const List scheduleTimeChoices = [ @@ -1029,7 +1106,7 @@ class _HeaderControlState extends State { }, ), SizedBox(width: buttonSpace), - if (showTitle && + if (isFullScreen.value && isLandscape && widget.videoType == SearchType.video) ...[ Column( @@ -1081,6 +1158,43 @@ class _HeaderControlState extends State { // ), // fuc: () => _.screenshot(), // ), + if (isFullScreen.value) ...[ + SizedBox( + width: 56, + height: 34, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => showShootDanmakuSheet(), + child: const Text( + '发弹幕', + style: textStyle, + ), + ), + ), + SizedBox( + width: 34, + height: 34, + child: Obx( + () => IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + _.isOpenDanmu.value = !_.isOpenDanmu.value; + }, + icon: Icon( + _.isOpenDanmu.value + ? Icons.subtitles_outlined + : Icons.subtitles_off_outlined, + size: 19, + color: Colors.white, + ), + ), + ), + ), + ], SizedBox(width: buttonSpace), if (Platform.isAndroid) ...[ SizedBox( From 2ba72e3792ef32f45ea9d1ac6568e584e7e22243 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 3 Apr 2024 23:59:24 +0800 Subject: [PATCH 04/18] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 237bd07f..470e9a35 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码 ```bash -[✓] Flutter (Channel stable, 3.16.4, on macOS 14.1.2 23B92 darwin-arm64, locale +[✓] Flutter (Channel stable, 3.16.5, on macOS 14.1.2 23B92 darwin-arm64, locale zh-Hans-CN) [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 15.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2022.3) -[✓] VS Code (version 1.85.1) +[✓] VS Code (version 1.87.2) [✓] Connected device (3 available) [✓] Network resources @@ -44,6 +44,9 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码 ## 技术交流 Telegram: https://t.me/+lm_oOVmF0RJiODk1 + +Tg Beta版本:@PiliPala_Beta + QQ频道: https://pd.qq.com/s/365esodk3 From 5500a58c32a23dfe5f684e6ecf5fd6a2b13d5b05 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 4 Apr 2024 23:41:16 +0800 Subject: [PATCH 05/18] =?UTF-8?q?mod:=20=E4=BC=98=E5=8C=96selectDialog?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/setting/widgets/select_dialog.dart | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index 72119755..50229f9e 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -44,6 +44,7 @@ class _SelectDialogState extends State> { setState(() { _tempValue = value as T; }); + Navigator.pop(context, _tempValue); }, ), ] @@ -51,19 +52,6 @@ class _SelectDialogState extends State> { ), ); }), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () => Navigator.pop(context, _tempValue), - child: const Text('确定'), - ) - ], ); } } From ec7762644b25fc6fdd4e6c3666391f390daacdac Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 5 Apr 2024 00:02:25 +0800 Subject: [PATCH 06/18] mod: findClosestNumber --- lib/pages/video/detail/controller.dart | 10 +++++----- lib/utils/utils.dart | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 5c4ac14b..c1a96132 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -91,7 +91,7 @@ class VideoDetailController extends GetxController late bool enableCDN; late int? cacheVideoQa; late String cacheDecode; - late int cacheAudioQa; + late int defaultAudioQa; PersistentBottomSheetController? replyReplyBottomSheetCtr; RxList subtitleContents = @@ -146,7 +146,7 @@ class VideoDetailController extends GetxController // 预设的解码格式 cacheDecode = setting.get(SettingBoxKey.defaultDecode, defaultValue: VideoDecodeFormats.values.last.code); - cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa, + defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa, defaultValue: AudioQuality.hiRes.code); oid.value = IdUtils.bv2av(Get.parameters['bvid']!); getSubtitle(); @@ -353,9 +353,9 @@ class VideoDetailController extends GetxController if (audiosList.isNotEmpty) { final List numbers = audiosList.map((map) => map.id!).toList(); - int closestNumber = Utils.findClosestNumber(cacheAudioQa, numbers); - if (!numbers.contains(cacheAudioQa) && - numbers.any((e) => e > cacheAudioQa)) { + int closestNumber = Utils.findClosestNumber(defaultAudioQa, numbers); + if (!numbers.contains(defaultAudioQa) && + numbers.any((e) => e > defaultAudioQa)) { closestNumber = 30280; } firstAudio = audiosList.firstWhere((e) => e.id == closestNumber); diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index cb7cbf25..a7273f05 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -209,6 +209,8 @@ class Utils { static int findClosestNumber(int target, List numbers) { int minDiff = 127; int closestNumber = 0; // 初始化为0,表示没有找到比目标值小的整数 + + // 向下查找 try { for (int number in numbers) { if (number < target) { @@ -221,6 +223,20 @@ class Utils { } } } catch (_) {} + + // 向上查找 + if (closestNumber == 0) { + try { + for (int number in numbers) { + int diff = (number - target).abs(); + + if (diff < minDiff) { + minDiff = diff; + closestNumber = number; + } + } + } catch (_) {} + } return closestNumber; } From a5494484aefe83fc4cb122ec1dcec9c72b9c67c8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Apr 2024 11:30:53 +0800 Subject: [PATCH 07/18] =?UTF-8?q?mod:=20=E6=9B=B4=E6=96=B0=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8=E5=BA=95=E6=A0=8F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/controller.dart | 9 ++ lib/pages/video/detail/view.dart | 2 + lib/plugin/pl_player/view.dart | 9 +- .../pl_player/widgets/bottom_control.dart | 87 +------------------ 4 files changed, 18 insertions(+), 89 deletions(-) diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index fe870873..227d6b84 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -21,6 +21,7 @@ import 'package:pilipala/utils/video_utils.dart'; import 'package:screen_brightness/screen_brightness.dart'; import '../../../http/danmaku.dart'; +import '../../../plugin/pl_player/models/bottom_control_type.dart'; import '../../../utils/id_utils.dart'; import 'widgets/header_control.dart'; @@ -94,6 +95,14 @@ class VideoDetailController extends GetxController PersistentBottomSheetController? replyReplyBottomSheetCtr; late bool enableRelatedVideo; + List subtitles = []; + RxList bottomList = [ + BottomControlType.playOrPause, + BottomControlType.time, + BottomControlType.space, + BottomControlType.fit, + BottomControlType.fullscreen, + ].obs; @override void onInit() { diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index a403e298..5ebbf62e 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -24,6 +24,7 @@ import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/utils/storage.dart'; +import '../../../plugin/pl_player/models/bottom_control_type.dart'; import '../../../services/shutdown_timer_service.dart'; import 'widgets/app_bar.dart'; @@ -298,6 +299,7 @@ class _VideoDetailPageState extends State playerController: plPlayerController!, ), ), + bottomList: vdCtr.bottomList, ); }, ); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 90861204..d32fc8e4 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -214,8 +214,8 @@ class _PLVideoPlayerState extends State /// 上一集 BottomControlType.pre: ComBtn( icon: const Icon( - Icons.skip_previous_outlined, - size: 15, + Icons.skip_previous_rounded, + size: 21, color: Colors.white, ), fuc: () {}, @@ -229,8 +229,8 @@ class _PLVideoPlayerState extends State /// 下一集 BottomControlType.next: ComBtn( icon: const Icon( - Icons.last_page_outlined, - size: 15, + Icons.skip_next_rounded, + size: 21, color: Colors.white, ), fuc: () {}, @@ -239,6 +239,7 @@ class _PLVideoPlayerState extends State /// 时间进度 BottomControlType.time: Row( children: [ + const SizedBox(width: 8), Obx(() { return Text( _.durationSeconds.value >= 3600 diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index ebb71b54..35e7792a 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -68,91 +68,8 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { ); }, ), - Row( - children: [...buildBottomControl!], - ), - // Row( - // children: [ - // PlayOrPauseButton( - // controller: _, - // ), - // const SizedBox(width: 4), - // // 播放时间 - // Obx(() { - // return Text( - // _.durationSeconds.value >= 3600 - // ? printDurationWithHours( - // Duration(seconds: _.positionSeconds.value)) - // : printDuration( - // Duration(seconds: _.positionSeconds.value)), - // style: textStyle, - // ); - // }), - // const SizedBox(width: 2), - // const Text('/', style: textStyle), - // const SizedBox(width: 2), - // Obx( - // () => Text( - // _.durationSeconds.value >= 3600 - // ? printDurationWithHours( - // Duration(seconds: _.durationSeconds.value)) - // : printDuration( - // Duration(seconds: _.durationSeconds.value)), - // style: textStyle, - // ), - // ), - // const Spacer(), - // // 倍速 - // // Obx( - // // () => SizedBox( - // // width: 45, - // // height: 34, - // // child: TextButton( - // // style: ButtonStyle( - // // padding: MaterialStateProperty.all(EdgeInsets.zero), - // // ), - // // onPressed: () { - // // _.togglePlaybackSpeed(); - // // }, - // // child: Text( - // // '${_.playbackSpeed.toString()}X', - // // style: textStyle, - // // ), - // // ), - // // ), - // // ), - // SizedBox( - // height: 30, - // child: TextButton( - // onPressed: () => _.toggleVideoFit(), - // style: ButtonStyle( - // padding: MaterialStateProperty.all(EdgeInsets.zero), - // ), - // child: Obx( - // () => Text( - // _.videoFitDEsc.value, - // style: const TextStyle(color: Colors.white, fontSize: 13), - // ), - // ), - // ), - // ), - // const SizedBox(width: 10), - // // 全屏 - // Obx( - // () => ComBtn( - // icon: Icon( - // _.isFullScreen.value - // ? FontAwesomeIcons.compress - // : FontAwesomeIcons.expand, - // size: 15, - // color: Colors.white, - // ), - // fuc: () => triggerFullScreen!(), - // ), - // ), - // ], - // ), - const SizedBox(height: 12), + Row(children: [...buildBottomControl!]), + const SizedBox(height: 10), ], ), ); From d4212f88c52704960b384e8565aae81ace88e47f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Apr 2024 15:26:53 +0800 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20=E6=8A=95=E7=A8=BF=E3=80=81?= =?UTF-8?q?=E7=95=AA=E5=89=A7=E5=90=88=E9=9B=86=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/pages_bottom_sheet.dart | 135 +++++++++ lib/models/common/video_episode_type.dart | 5 + lib/pages/bangumi/introduction/view.dart | 5 +- lib/pages/bangumi/widgets/bangumi_panel.dart | 227 +++++---------- .../video/detail/introduction/controller.dart | 10 +- lib/pages/video/detail/introduction/view.dart | 25 +- .../detail/introduction/widgets/page.dart | 258 ------------------ .../introduction/widgets/page_panel.dart | 184 +++++++++++++ .../{season.dart => season_panel.dart} | 96 ++----- lib/pages/video/detail/view.dart | 1 + .../video/detail/widgets/header_control.dart | 3 +- .../video/detail/widgets/right_drawer.dart | 19 ++ 12 files changed, 458 insertions(+), 510 deletions(-) create mode 100644 lib/common/pages_bottom_sheet.dart create mode 100644 lib/models/common/video_episode_type.dart delete mode 100644 lib/pages/video/detail/introduction/widgets/page.dart create mode 100644 lib/pages/video/detail/introduction/widgets/page_panel.dart rename lib/pages/video/detail/introduction/widgets/{season.dart => season_panel.dart} (56%) create mode 100644 lib/pages/video/detail/widgets/right_drawer.dart diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart new file mode 100644 index 00000000..b31ec4b1 --- /dev/null +++ b/lib/common/pages_bottom_sheet.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import '../models/common/video_episode_type.dart'; + +class EpisodeBottomSheet { + final List episodes; + final int currentCid; + final dynamic dataType; + final BuildContext context; + final Function changeFucCall; + final int? cid; + final double? sheetHeight; + + EpisodeBottomSheet({ + required this.episodes, + required this.currentCid, + required this.dataType, + required this.context, + required this.changeFucCall, + this.cid, + this.sheetHeight, + }); + + Widget buildEpisodeListItem( + dynamic episode, + int index, + bool isCurrentIndex, + ) { + Color primary = Theme.of(context).colorScheme.primary; + Color onSurface = Theme.of(context).colorScheme.onSurface; + + String title = ''; + switch (dataType) { + case VideoEpidoesType.videoEpisode: + title = episode.title; + break; + case VideoEpidoesType.videoPart: + title = episode.pagePart; + break; + case VideoEpidoesType.bangumiEpisode: + title = '第${episode.title}话 ${episode.longTitle!}'; + break; + } + return ListTile( + onTap: () { + SmartDialog.showToast('切换至「$title」'); + changeFucCall.call(episode, index); + }, + dense: false, + leading: isCurrentIndex + ? Image.asset( + 'assets/images/live.gif', + color: primary, + height: 12, + ) + : null, + title: Text( + title, + style: TextStyle( + fontSize: 14, + color: isCurrentIndex ? primary : onSurface, + ), + ), + ); + } + + Widget buildTitle() { + return AppBar( + toolbarHeight: 45, + automaticallyImplyLeading: false, + centerTitle: false, + title: Text( + '合集(${episodes.length})', + style: Theme.of(context).textTheme.titleMedium, + ), + actions: [ + IconButton( + icon: const Icon(Icons.close, size: 20), + onPressed: () => Navigator.pop(context), + ), + const SizedBox(width: 14), + ], + ); + } + + /// The [BuildContext] of the widget that calls the bottom sheet. + PersistentBottomSheetController show(BuildContext context) { + final ItemScrollController itemScrollController = ItemScrollController(); + int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid); + final PersistentBottomSheetController btmSheetCtr = showBottomSheet( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + itemScrollController.jumpTo(index: currentIndex); + }); + return Container( + height: sheetHeight, + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + buildTitle(), + Expanded( + child: Material( + child: ScrollablePositionedList.builder( + itemScrollController: itemScrollController, + itemCount: episodes.length + 1, + itemBuilder: (BuildContext context, int index) { + bool isLastItem = index == episodes.length; + bool isCurrentIndex = currentIndex == index; + return isLastItem + ? SizedBox( + height: + MediaQuery.of(context).padding.bottom + 20, + ) + : buildEpisodeListItem( + episodes[index], + index, + isCurrentIndex, + ); + }, + ), + ), + ), + ], + ), + ); + }); + }, + ); + return btmSheetCtr; + } +} diff --git a/lib/models/common/video_episode_type.dart b/lib/models/common/video_episode_type.dart new file mode 100644 index 00000000..4875438f --- /dev/null +++ b/lib/models/common/video_episode_type.dart @@ -0,0 +1,5 @@ +enum VideoEpidoesType { + videoEpisode, + videoPart, + bangumiEpisode, +} diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 6255ffda..13db7432 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -138,6 +138,9 @@ class _BangumiInfoState extends State { cid = widget.cid!; videoDetailCtr.cid.listen((p0) { cid = p0; + if (!mounted) { + return; + } setState(() {}); }); } @@ -317,7 +320,7 @@ class _BangumiInfoState extends State { if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[ BangumiPanel( pages: widget.bangumiDetail!.episodes!, - cid: cid ?? widget.bangumiDetail!.episodes!.first.cid, + cid: cid! ?? widget.bangumiDetail!.episodes!.first.cid!, sheetHeight: sheetHeight, changeFuc: (bvid, cid, aid) => bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid), diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index 05fd814c..988f8d4f 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -6,19 +8,21 @@ import 'package:pilipala/models/bangumi/info.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import '../../../common/pages_bottom_sheet.dart'; +import '../../../models/common/video_episode_type.dart'; class BangumiPanel extends StatefulWidget { const BangumiPanel({ super.key, required this.pages, - this.cid, + required this.cid, this.sheetHeight, this.changeFuc, this.bangumiDetail, }); final List pages; - final int? cid; + final int cid; final double? sheetHeight; final Function? changeFuc; final BangumiInfoModel? bangumiDetail; @@ -28,9 +32,8 @@ class BangumiPanel extends StatefulWidget { } class _BangumiPanelState extends State { - late int currentIndex; + late RxInt currentIndex = (-1).obs; final ScrollController listViewScrollCtr = ScrollController(); - final ScrollController listViewScrollCtr_2 = ScrollController(); Box userInfoCache = GStrorage.userInfo; dynamic userInfo; // 默认未开通 @@ -39,169 +42,68 @@ class _BangumiPanelState extends State { String heroTag = Get.arguments['heroTag']; late final VideoDetailController videoDetailCtr; final ItemScrollController itemScrollController = ItemScrollController(); + late PersistentBottomSheetController? _bottomSheetController; @override void initState() { super.initState(); - cid = widget.cid!; - currentIndex = widget.pages.indexWhere((e) => e.cid == cid); + cid = widget.cid; + videoDetailCtr = Get.find(tag: heroTag); + currentIndex.value = + widget.pages.indexWhere((EpisodeItem e) => e.cid == cid); scrollToIndex(); + videoDetailCtr.cid.listen((int p0) { + cid = p0; + currentIndex.value = + widget.pages.indexWhere((EpisodeItem e) => e.cid == cid); + scrollToIndex(); + }); + + /// 获取大会员状态 userInfo = userInfoCache.get('userInfoCache'); if (userInfo != null) { vipStatus = userInfo.vipStatus; } - videoDetailCtr = Get.find(tag: heroTag); - - videoDetailCtr.cid.listen((int p0) { - cid = p0; - setState(() {}); - currentIndex = widget.pages.indexWhere((EpisodeItem e) => e.cid == cid); - scrollToIndex(); - }); } @override void dispose() { listViewScrollCtr.dispose(); - listViewScrollCtr_2.dispose(); super.dispose(); } - Widget buildPageListItem( - EpisodeItem page, - int index, - bool isCurrentIndex, - ) { - Color primary = Theme.of(context).colorScheme.primary; - return ListTile( - onTap: () { - Get.back(); - setState(() { - changeFucCall(page, index); - }); - }, - dense: false, - leading: isCurrentIndex - ? Image.asset( - 'assets/images/live.gif', - color: primary, - height: 12, - ) - : null, - title: Text( - '第${page.title}话 ${page.longTitle!}', - style: TextStyle( - fontSize: 14, - color: isCurrentIndex - ? primary - : Theme.of(context).colorScheme.onSurface, - ), - ), - trailing: page.badge != null - ? Text( - page.badge!, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - ) - : const SizedBox(), - ); - } - - void showBangumiPanel() { - showBottomSheet( - context: context, - builder: (BuildContext context) { - return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - // await Future.delayed(const Duration(milliseconds: 200)); - // listViewScrollCtr_2.animateTo(currentIndex * 56, - // duration: const Duration(milliseconds: 500), - // curve: Curves.easeInOut); - itemScrollController.jumpTo(index: currentIndex); - }); - // 在这里使用 setState 更新状态 - return Container( - height: widget.sheetHeight, - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - AppBar( - toolbarHeight: 45, - automaticallyImplyLeading: false, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '合集(${widget.pages.length})', - style: Theme.of(context).textTheme.titleMedium, - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), - titleSpacing: 10, - ), - Expanded( - child: Material( - child: ScrollablePositionedList.builder( - itemCount: widget.pages.length + 1, - itemBuilder: (BuildContext context, int index) { - bool isLastItem = index == widget.pages.length; - bool isCurrentIndex = currentIndex == index; - return isLastItem - ? SizedBox( - height: - MediaQuery.of(context).padding.bottom + - 20, - ) - : buildPageListItem( - widget.pages[index], - index, - isCurrentIndex, - ); - }, - itemScrollController: itemScrollController, - ), - ), - ), - ], - ), - ); - }, - ); - }, - ); - } - void changeFucCall(item, i) async { if (item.badge != null && item.badge == '会员' && vipStatus != 1) { SmartDialog.showToast('需要大会员'); return; } - await widget.changeFuc!( + widget.changeFuc?.call( item.bvid, item.cid, item.aid, ); + _bottomSheetController?.close(); currentIndex = i; - setState(() {}); scrollToIndex(); } void scrollToIndex() { WidgetsBinding.instance.addPostFrameCallback((_) { // 在回调函数中获取更新后的状态 - listViewScrollCtr.animateTo(currentIndex * 150, - duration: const Duration(milliseconds: 500), curve: Curves.easeInOut); + final double offset = min((currentIndex * 150) - 75, + listViewScrollCtr.position.maxScrollExtent); + listViewScrollCtr.animateTo( + offset, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); }); } @override Widget build(BuildContext context) { + Color primary = Theme.of(context).colorScheme.primary; + Color onSurface = Theme.of(context).colorScheme.onSurface; return Column( children: [ Padding( @@ -211,12 +113,14 @@ class _BangumiPanelState extends State { children: [ const Text('选集 '), Expanded( - child: Text( - ' 正在播放:${widget.pages[currentIndex].longTitle}', - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, + child: Obx( + () => Text( + ' 正在播放:${widget.pages[currentIndex.value].longTitle}', + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), ), ), ), @@ -227,7 +131,16 @@ class _BangumiPanelState extends State { style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), - onPressed: () => showBangumiPanel(), + onPressed: () { + _bottomSheetController = EpisodeBottomSheet( + currentCid: cid, + episodes: widget.pages, + changeFucCall: changeFucCall, + sheetHeight: widget.sheetHeight, + dataType: VideoEpidoesType.bangumiEpisode, + context: context, + ).show(context); + }, child: Text( '${widget.bangumiDetail!.newEp!['desc']}', style: const TextStyle(fontSize: 13), @@ -245,6 +158,8 @@ class _BangumiPanelState extends State { itemCount: widget.pages.length, itemExtent: 150, itemBuilder: (BuildContext context, int i) { + var page = widget.pages[i]; + bool isSelected = i == currentIndex.value; return Container( width: 150, margin: const EdgeInsets.only(right: 10), @@ -253,42 +168,37 @@ class _BangumiPanelState extends State { borderRadius: BorderRadius.circular(6), clipBehavior: Clip.hardEdge, child: InkWell( - onTap: () => changeFucCall(widget.pages[i], i), + onTap: () => changeFucCall(page, i), child: Padding( padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 10), + vertical: 8, + horizontal: 10, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - if (i == currentIndex) ...[ - Image.asset( - 'assets/images/live.png', - color: Theme.of(context).colorScheme.primary, - height: 12, - ), + if (isSelected) ...[ + Image.asset('assets/images/live.png', + color: primary, height: 12), const SizedBox(width: 6) ], Text( '第${i + 1}话', style: TextStyle( - fontSize: 13, - color: i == currentIndex - ? Theme.of(context).colorScheme.primary - : Theme.of(context) - .colorScheme - .onSurface), + fontSize: 13, + color: isSelected ? primary : onSurface, + ), ), const SizedBox(width: 2), - if (widget.pages[i].badge != null) ...[ + if (page.badge != null) ...[ const Spacer(), Text( - widget.pages[i].badge!, + page.badge!, style: TextStyle( fontSize: 12, - color: - Theme.of(context).colorScheme.primary, + color: primary, ), ), ] @@ -296,13 +206,12 @@ class _BangumiPanelState extends State { ), const SizedBox(height: 3), Text( - widget.pages[i].longTitle!, + page.longTitle!, maxLines: 1, style: TextStyle( - fontSize: 13, - color: i == currentIndex - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface), + fontSize: 13, + color: isSelected ? primary : onSurface, + ), overflow: TextOverflow.ellipsis, ) ], diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 8114bdaf..241605ab 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -25,15 +25,10 @@ class VideoIntroController extends GetxController { VideoIntroController({required this.bvid}); // 视频bvid String bvid; - // 请求状态 - RxBool isLoading = false.obs; - // 视频详情 请求返回 Rx videoDetail = VideoDetailData().obs; - // up主粉丝数 Map userStat = {'follower': '-'}; - // 是否点赞 RxBool hasLike = false.obs; // 是否投币 @@ -59,6 +54,7 @@ class VideoIntroController extends GetxController { bool isPaused = false; String heroTag = ''; late ModelResult modelResult; + late PersistentBottomSheetController? bottomSheetController; @override void onInit() { @@ -562,4 +558,8 @@ class VideoIntroController extends GetxController { } return res; } + + hiddenEpisodeBottomSheet() { + bottomSheetController?.close(); + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index a990aab8..608beec8 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -20,8 +20,8 @@ import '../../../../http/user.dart'; import 'widgets/action_item.dart'; import 'widgets/fav_panel.dart'; import 'widgets/intro_detail.dart'; -import 'widgets/page.dart'; -import 'widgets/season.dart'; +import 'widgets/page_panel.dart'; +import 'widgets/season_panel.dart'; class VideoIntroPanel extends StatefulWidget { final String bvid; @@ -384,18 +384,25 @@ class _VideoInfoState extends State with TickerProviderStateMixin { sheetHeight: sheetHeight, changeFuc: (bvid, cid, aid) => videoIntroController.changeSeasonOrbangu(bvid, cid, aid), + videoIntroCtr: videoIntroController, ), ) ], if (widget.videoDetail!.pages != null && widget.videoDetail!.pages!.length > 1) ...[ - Obx(() => PagesPanel( - pages: widget.videoDetail!.pages!, - cid: videoIntroController.lastPlayCid.value, - sheetHeight: sheetHeight, - changeFuc: (cid) => videoIntroController.changeSeasonOrbangu( - videoIntroController.bvid, cid, null), - )) + Obx( + () => PagesPanel( + pages: widget.videoDetail!.pages!, + cid: videoIntroController.lastPlayCid.value, + sheetHeight: sheetHeight, + changeFuc: (cid) => videoIntroController.changeSeasonOrbangu( + videoIntroController.bvid, + cid, + null, + ), + videoIntroCtr: videoIntroController, + ), + ) ], GestureDetector( onTap: onPushMember, diff --git a/lib/pages/video/detail/introduction/widgets/page.dart b/lib/pages/video/detail/introduction/widgets/page.dart deleted file mode 100644 index 8d296050..00000000 --- a/lib/pages/video/detail/introduction/widgets/page.dart +++ /dev/null @@ -1,258 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:pilipala/models/video_detail_res.dart'; -import 'package:pilipala/pages/video/detail/index.dart'; - -class PagesPanel extends StatefulWidget { - const PagesPanel({ - super.key, - required this.pages, - this.cid, - this.sheetHeight, - this.changeFuc, - }); - final List pages; - final int? cid; - final double? sheetHeight; - final Function? changeFuc; - - @override - State createState() => _PagesPanelState(); -} - -class _PagesPanelState extends State { - late List episodes; - late int cid; - late int currentIndex; - final String heroTag = Get.arguments['heroTag']; - late VideoDetailController _videoDetailController; - final ScrollController _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - cid = widget.cid!; - episodes = widget.pages; - _videoDetailController = Get.find(tag: heroTag); - currentIndex = episodes.indexWhere((Part e) => e.cid == cid); - _videoDetailController.cid.listen((int p0) { - cid = p0; - setState(() {}); - currentIndex = episodes.indexWhere((Part e) => e.cid == cid); - }); - } - - void changeFucCall(item, i) async { - await widget.changeFuc!( - item.cid, - ); - currentIndex = i; - setState(() {}); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - Widget buildEpisodeListItem( - Part episode, - int index, - bool isCurrentIndex, - ) { - Color primary = Theme.of(context).colorScheme.primary; - return ListTile( - onTap: () { - changeFucCall(episode, index); - Get.back(); - }, - dense: false, - leading: isCurrentIndex - ? Image.asset( - 'assets/images/live.gif', - color: primary, - height: 12, - ) - : null, - title: Text( - episode.pagePart!, - style: TextStyle( - fontSize: 14, - color: isCurrentIndex - ? primary - : Theme.of(context).colorScheme.onSurface, - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 10, bottom: 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('视频选集 '), - Expanded( - child: Text( - ' 正在播放:${widget.pages[currentIndex].pagePart}', - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - const SizedBox(width: 10), - SizedBox( - height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - showBottomSheet( - context: context, - builder: (BuildContext context) { - return StatefulBuilder(builder: - (BuildContext context, StateSetter setState) { - WidgetsBinding.instance - .addPostFrameCallback((_) async { - await Future.delayed( - const Duration(milliseconds: 200)); - _scrollController.jumpTo(currentIndex * 56); - }); - return Container( - height: widget.sheetHeight, - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - Container( - height: 45, - padding: const EdgeInsets.only( - left: 14, right: 14), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - '合集(${episodes.length})', - style: Theme.of(context) - .textTheme - .titleMedium, - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ), - Divider( - height: 1, - color: Theme.of(context) - .dividerColor - .withOpacity(0.1), - ), - Expanded( - child: Material( - child: ListView.builder( - controller: _scrollController, - itemCount: episodes.length + 1, - itemBuilder: - (BuildContext context, int index) { - bool isLastItem = - index == episodes.length; - bool isCurrentIndex = - currentIndex == index; - return isLastItem - ? SizedBox( - height: MediaQuery.of(context) - .padding - .bottom + - 20, - ) - : buildEpisodeListItem( - episodes[index], - index, - isCurrentIndex, - ); - }, - ), - ), - ), - ], - ), - ); - }); - }, - ); - }, - child: Text( - '共${widget.pages.length}集', - style: const TextStyle(fontSize: 13), - ), - ), - ), - ], - ), - ), - Container( - height: 35, - margin: const EdgeInsets.only(bottom: 8), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: widget.pages.length, - itemExtent: 150, - itemBuilder: (BuildContext context, int i) { - bool isCurrentIndex = currentIndex == i; - return Container( - width: 150, - margin: const EdgeInsets.only(right: 10), - child: Material( - color: Theme.of(context).colorScheme.onInverseSurface, - borderRadius: BorderRadius.circular(6), - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () => changeFucCall(widget.pages[i], i), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 8), - child: Row( - children: [ - if (isCurrentIndex) ...[ - Image.asset( - 'assets/images/live.gif', - color: Theme.of(context).colorScheme.primary, - height: 12, - ), - const SizedBox(width: 6) - ], - Expanded( - child: Text( - widget.pages[i].pagePart!, - maxLines: 1, - style: TextStyle( - fontSize: 13, - color: isCurrentIndex - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface), - overflow: TextOverflow.ellipsis, - )) - ], - ), - ), - ), - ), - ); - }, - ), - ) - ], - ); - } -} diff --git a/lib/pages/video/detail/introduction/widgets/page_panel.dart b/lib/pages/video/detail/introduction/widgets/page_panel.dart new file mode 100644 index 00000000..730a6105 --- /dev/null +++ b/lib/pages/video/detail/introduction/widgets/page_panel.dart @@ -0,0 +1,184 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/models/video_detail_res.dart'; +import 'package:pilipala/pages/video/detail/index.dart'; +import 'package:pilipala/pages/video/detail/introduction/index.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import '../../../../../common/pages_bottom_sheet.dart'; +import '../../../../../models/common/video_episode_type.dart'; + +class PagesPanel extends StatefulWidget { + const PagesPanel({ + super.key, + required this.pages, + required this.cid, + this.sheetHeight, + this.changeFuc, + required this.videoIntroCtr, + }); + final List pages; + final int cid; + final double? sheetHeight; + final Function? changeFuc; + final VideoIntroController videoIntroCtr; + + @override + State createState() => _PagesPanelState(); +} + +class _PagesPanelState extends State { + late List episodes; + late int cid; + late RxInt currentIndex = (-1).obs; + final String heroTag = Get.arguments['heroTag']; + late VideoDetailController _videoDetailController; + final ScrollController listViewScrollCtr = ScrollController(); + final ItemScrollController itemScrollController = ItemScrollController(); + late PersistentBottomSheetController? _bottomSheetController; + + @override + void initState() { + super.initState(); + cid = widget.cid; + episodes = widget.pages; + _videoDetailController = Get.find(tag: heroTag); + currentIndex.value = episodes.indexWhere((Part e) => e.cid == cid); + scrollToIndex(); + _videoDetailController.cid.listen((int p0) { + cid = p0; + currentIndex.value = episodes.indexWhere((Part e) => e.cid == cid); + scrollToIndex(); + }); + } + + @override + void dispose() { + listViewScrollCtr.dispose(); + super.dispose(); + } + + void changeFucCall(item, i) async { + widget.changeFuc?.call(item.cid); + currentIndex.value = i; + _bottomSheetController?.close(); + scrollToIndex(); + } + + void scrollToIndex() { + WidgetsBinding.instance.addPostFrameCallback((_) { + // 在回调函数中获取更新后的状态 + final double offset = min((currentIndex * 150) - 75, + listViewScrollCtr.position.maxScrollExtent); + listViewScrollCtr.animateTo( + offset, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('视频选集 '), + Expanded( + child: Obx(() => Text( + ' 正在播放:${widget.pages[currentIndex.value].pagePart}', + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + )), + ), + const SizedBox(width: 10), + SizedBox( + height: 34, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + widget.videoIntroCtr.bottomSheetController = + _bottomSheetController = EpisodeBottomSheet( + currentCid: cid, + episodes: episodes, + changeFucCall: changeFucCall, + sheetHeight: widget.sheetHeight, + dataType: VideoEpidoesType.videoPart, + context: context, + ).show(context); + }, + child: Text( + '共${widget.pages.length}集', + style: const TextStyle(fontSize: 13), + ), + ), + ), + ], + ), + ), + Container( + height: 35, + margin: const EdgeInsets.only(bottom: 8), + child: ListView.builder( + scrollDirection: Axis.horizontal, + controller: listViewScrollCtr, + itemCount: widget.pages.length, + itemExtent: 150, + itemBuilder: (BuildContext context, int i) { + bool isCurrentIndex = currentIndex.value == i; + return Container( + width: 150, + margin: const EdgeInsets.only(right: 10), + child: Material( + color: Theme.of(context).colorScheme.onInverseSurface, + borderRadius: BorderRadius.circular(6), + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () => changeFucCall(widget.pages[i], i), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, horizontal: 8), + child: Row( + children: [ + if (isCurrentIndex) ...[ + Image.asset( + 'assets/images/live.gif', + color: Theme.of(context).colorScheme.primary, + height: 12, + ), + const SizedBox(width: 6) + ], + Expanded( + child: Text( + widget.pages[i].pagePart!, + maxLines: 1, + style: TextStyle( + fontSize: 13, + color: isCurrentIndex + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface), + overflow: TextOverflow.ellipsis, + )) + ], + ), + ), + ), + ), + ); + }, + ), + ) + ], + ); + } +} diff --git a/lib/pages/video/detail/introduction/widgets/season.dart b/lib/pages/video/detail/introduction/widgets/season_panel.dart similarity index 56% rename from lib/pages/video/detail/introduction/widgets/season.dart rename to lib/pages/video/detail/introduction/widgets/season_panel.dart index 0f3884ed..cd8bc954 100644 --- a/lib/pages/video/detail/introduction/widgets/season.dart +++ b/lib/pages/video/detail/introduction/widgets/season_panel.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/pages_bottom_sheet.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import '../../../../../models/common/video_episode_type.dart'; +import '../controller.dart'; class SeasonPanel extends StatefulWidget { const SeasonPanel({ @@ -12,11 +15,13 @@ class SeasonPanel extends StatefulWidget { this.cid, this.sheetHeight, this.changeFuc, + required this.videoIntroCtr, }); final UgcSeason ugcSeason; final int? cid; final double? sheetHeight; final Function? changeFuc; + final VideoIntroController videoIntroCtr; @override State createState() => _SeasonPanelState(); @@ -28,8 +33,8 @@ class _SeasonPanelState extends State { late int currentIndex; final String heroTag = Get.arguments['heroTag']; late VideoDetailController _videoDetailController; - final ScrollController _scrollController = ScrollController(); final ItemScrollController itemScrollController = ItemScrollController(); + late PersistentBottomSheetController? _bottomSheetController; @override void initState() { @@ -52,9 +57,6 @@ class _SeasonPanelState extends State { } /// 取对应 season_id 的 episodes - // episodes = widget.ugcSeason.sections! - // .firstWhere((e) => e.seasonId == widget.ugcSeason.id) - // .episodes!; currentIndex = episodes.indexWhere((EpisodeItem e) => e.cid == cid); _videoDetailController.cid.listen((int p0) { cid = p0; @@ -64,22 +66,16 @@ class _SeasonPanelState extends State { } void changeFucCall(item, int i) async { - await widget.changeFuc!( + widget.changeFuc?.call( IdUtils.av2bv(item.aid), item.cid, item.aid, ); currentIndex = i; - Get.back(); + _bottomSheetController?.close(); setState(() {}); } - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - Widget buildEpisodeListItem( EpisodeItem episode, int index, @@ -123,71 +119,17 @@ class _SeasonPanelState extends State { borderRadius: BorderRadius.circular(6), clipBehavior: Clip.hardEdge, child: InkWell( - onTap: () => showBottomSheet( - context: context, - builder: (BuildContext context) { - return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - itemScrollController.jumpTo(index: currentIndex); - }); - return Container( - height: widget.sheetHeight, - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - Container( - height: 45, - padding: const EdgeInsets.only(left: 14, right: 14), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '合集(${episodes.length})', - style: Theme.of(context).textTheme.titleMedium, - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ), - Divider( - height: 1, - color: - Theme.of(context).dividerColor.withOpacity(0.1), - ), - Expanded( - child: Material( - child: ScrollablePositionedList.builder( - itemCount: episodes.length + 1, - itemBuilder: (BuildContext context, int index) { - bool isLastItem = index == episodes.length; - bool isCurrentIndex = currentIndex == index; - return isLastItem - ? SizedBox( - height: MediaQuery.of(context) - .padding - .bottom + - 20, - ) - : buildEpisodeListItem( - episodes[index], - index, - isCurrentIndex, - ); - }, - itemScrollController: itemScrollController, - ), - ), - ), - ], - ), - ); - }); - }, - ), + onTap: () { + widget.videoIntroCtr.bottomSheetController = + _bottomSheetController = EpisodeBottomSheet( + currentCid: cid, + episodes: episodes, + changeFucCall: changeFucCall, + sheetHeight: widget.sheetHeight, + dataType: VideoEpidoesType.videoEpisode, + context: context, + ).show(context); + }, child: Padding( padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), child: Row( diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index c2379f20..725639ff 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -176,6 +176,7 @@ class _VideoDetailPageState extends State plPlayerController?.isFullScreen.listen((bool isFullScreen) { if (isFullScreen) { vdCtr.hiddenReplyReplyPanel(); + videoIntroController.hiddenEpisodeBottomSheet(); } }); } diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 1ee65d83..4a8f4759 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -82,6 +82,7 @@ class _HeaderControlState extends State { /// 设置面板 void showSettingSheet() { + // Scaffold.of(context).openDrawer(); showModalBottomSheet( elevation: 0, context: context, @@ -158,7 +159,7 @@ class _HeaderControlState extends State { dense: true, leading: const Icon(Icons.hourglass_top_outlined, size: 20), - title: const Text('定时关闭(测试)', style: titleStyle), + title: const Text('定时关闭', style: titleStyle), ), ListTile( onTap: () => {Get.back(), showSetVideoQa()}, diff --git a/lib/pages/video/detail/widgets/right_drawer.dart b/lib/pages/video/detail/widgets/right_drawer.dart new file mode 100644 index 00000000..ca0d34ef --- /dev/null +++ b/lib/pages/video/detail/widgets/right_drawer.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class RightDrawer extends StatefulWidget { + const RightDrawer({super.key}); + + @override + State createState() => _RightDrawerState(); +} + +class _RightDrawerState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + shadowColor: Colors.transparent, + elevation: 0, + backgroundColor: + Theme.of(context).colorScheme.surface.withOpacity(0.8)); + } +} From c2a4d80c79c9a2824c13505cc559035bb9d8d046 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Apr 2024 21:32:29 +0800 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20=E5=85=A8=E5=B1=8F=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=90=88=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/pages_bottom_sheet.dart | 103 ++++++++++-------- .../bangumi/introduction/controller.dart | 29 +++++ .../video/detail/introduction/controller.dart | 49 ++++++++- lib/pages/video/detail/introduction/view.dart | 9 +- .../introduction/widgets/page_panel.dart | 1 + .../introduction/widgets/season_panel.dart | 18 ++- lib/pages/video/detail/view.dart | 18 ++- .../pl_player/models/bottom_control_type.dart | 1 + lib/plugin/pl_player/view.dart | 20 ++++ lib/utils/drawer.dart | 39 +++++++ 10 files changed, 222 insertions(+), 65 deletions(-) create mode 100644 lib/utils/drawer.dart diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart index b31ec4b1..c64b58b6 100644 --- a/lib/common/pages_bottom_sheet.dart +++ b/lib/common/pages_bottom_sheet.dart @@ -11,6 +11,7 @@ class EpisodeBottomSheet { final Function changeFucCall; final int? cid; final double? sheetHeight; + bool isFullScreen = false; EpisodeBottomSheet({ required this.episodes, @@ -20,6 +21,7 @@ class EpisodeBottomSheet { required this.changeFucCall, this.cid, this.sheetHeight, + this.isFullScreen = false, }); Widget buildEpisodeListItem( @@ -74,60 +76,69 @@ class EpisodeBottomSheet { '合集(${episodes.length})', style: Theme.of(context).textTheme.titleMedium, ), - actions: [ - IconButton( - icon: const Icon(Icons.close, size: 20), - onPressed: () => Navigator.pop(context), - ), - const SizedBox(width: 14), - ], + actions: !isFullScreen + ? [ + IconButton( + icon: const Icon(Icons.close, size: 20), + onPressed: () => Navigator.pop(context), + ), + const SizedBox(width: 14), + ] + : null, ); } + Widget buildShowContent(BuildContext context) { + final ItemScrollController itemScrollController = ItemScrollController(); + int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid); + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + WidgetsBinding.instance.addPostFrameCallback((_) { + itemScrollController.jumpTo(index: currentIndex); + }); + return Container( + height: sheetHeight, + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + buildTitle(), + Expanded( + child: Material( + child: PageStorage( + bucket: PageStorageBucket(), + child: ScrollablePositionedList.builder( + itemScrollController: itemScrollController, + itemCount: episodes.length + 1, + itemBuilder: (BuildContext context, int index) { + bool isLastItem = index == episodes.length; + bool isCurrentIndex = currentIndex == index; + return isLastItem + ? SizedBox( + height: + MediaQuery.of(context).padding.bottom + 20, + ) + : buildEpisodeListItem( + episodes[index], + index, + isCurrentIndex, + ); + }, + ), + ), + ), + ), + ], + ), + ); + }); + } + /// The [BuildContext] of the widget that calls the bottom sheet. PersistentBottomSheetController show(BuildContext context) { - final ItemScrollController itemScrollController = ItemScrollController(); - int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid); final PersistentBottomSheetController btmSheetCtr = showBottomSheet( context: context, builder: (BuildContext context) { - return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - WidgetsBinding.instance.addPostFrameCallback((_) { - itemScrollController.jumpTo(index: currentIndex); - }); - return Container( - height: sheetHeight, - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - buildTitle(), - Expanded( - child: Material( - child: ScrollablePositionedList.builder( - itemScrollController: itemScrollController, - itemCount: episodes.length + 1, - itemBuilder: (BuildContext context, int index) { - bool isLastItem = index == episodes.length; - bool isCurrentIndex = currentIndex == index; - return isLastItem - ? SizedBox( - height: - MediaQuery.of(context).padding.bottom + 20, - ) - : buildEpisodeListItem( - episodes[index], - index, - isCurrentIndex, - ); - }, - ), - ), - ), - ], - ), - ); - }); + return buildShowContent(context); }, ); return btmSheetCtr; diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index 12f0c053..63eadacf 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -15,6 +15,10 @@ import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:share_plus/share_plus.dart'; +import '../../../common/pages_bottom_sheet.dart'; +import '../../../models/common/video_episode_type.dart'; +import '../../../utils/drawer.dart'; + class BangumiIntroController extends GetxController { // 视频bvid String bvid = Get.parameters['bvid']!; @@ -291,4 +295,29 @@ class BangumiIntroController extends GetxController { int aid = episodes[nextIndex].aid!; changeSeasonOrbangu(bvid, cid, aid); } + + // 播放器底栏 选集 回调 + void showEposideHandler() { + late List episodes = bangumiDetail.value.episodes!; + VideoEpidoesType dataType = VideoEpidoesType.bangumiEpisode; + if (episodes.isEmpty) { + return; + } + VideoDetailController videoDetailCtr = + Get.find(tag: Get.arguments['heroTag']); + DrawerUtils.showRightDialog( + child: EpisodeBottomSheet( + episodes: episodes, + currentCid: videoDetailCtr.cid.value, + dataType: dataType, + context: Get.context!, + sheetHeight: Get.size.height, + isFullScreen: true, + changeFucCall: (item, index) { + changeSeasonOrbangu(item.bvid, item.cid, item.aid); + SmartDialog.dismiss(); + }, + ).buildShowContent(Get.context!), + ); + } } diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 241605ab..d81bda00 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -18,6 +18,9 @@ import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:share_plus/share_plus.dart'; +import '../../../../common/pages_bottom_sheet.dart'; +import '../../../../models/common/video_episode_type.dart'; +import '../../../../utils/drawer.dart'; import '../related/index.dart'; import 'widgets/group_panel.dart'; @@ -54,7 +57,7 @@ class VideoIntroController extends GetxController { bool isPaused = false; String heroTag = ''; late ModelResult modelResult; - late PersistentBottomSheetController? bottomSheetController; + PersistentBottomSheetController? bottomSheetController; @override void onInit() { @@ -562,4 +565,48 @@ class VideoIntroController extends GetxController { hiddenEpisodeBottomSheet() { bottomSheetController?.close(); } + + // 播放器底栏 选集 回调 + void showEposideHandler() { + late List episodes; + VideoEpidoesType dataType = VideoEpidoesType.videoEpisode; + if (videoDetail.value.ugcSeason != null) { + dataType = VideoEpidoesType.videoEpisode; + final List sections = videoDetail.value.ugcSeason!.sections!; + for (int i = 0; i < sections.length; i++) { + final List episodesList = sections[i].episodes!; + for (int j = 0; j < episodesList.length; j++) { + if (episodesList[j].cid == lastPlayCid.value) { + episodes = episodesList; + continue; + } + } + } + } + if (videoDetail.value.pages != null && + videoDetail.value.pages!.length > 1) { + dataType = VideoEpidoesType.videoPart; + episodes = videoDetail.value.pages!; + } + + DrawerUtils.showRightDialog( + child: EpisodeBottomSheet( + episodes: episodes, + currentCid: lastPlayCid.value, + dataType: dataType, + context: Get.context!, + sheetHeight: Get.size.height, + isFullScreen: true, + changeFucCall: (item, index) { + if (dataType == VideoEpidoesType.videoEpisode) { + changeSeasonOrbangu(IdUtils.av2bv(item.aid), item.cid, item.aid); + } + if (dataType == VideoEpidoesType.videoPart) { + changeSeasonOrbangu(bvid, item.cid, null); + } + SmartDialog.dismiss(); + }, + ).buildShowContent(Get.context!), + ); + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 608beec8..70fa578d 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -373,7 +373,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { /// 点赞收藏转发 actionGrid(context, videoIntroController), - // 合集 + // 合集 videoPart 简洁 if (widget.videoDetail!.ugcSeason != null) ...[ Obx( () => SeasonPanel( @@ -383,11 +383,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { : widget.videoDetail!.pages!.first.cid, sheetHeight: sheetHeight, changeFuc: (bvid, cid, aid) => - videoIntroController.changeSeasonOrbangu(bvid, cid, aid), + videoIntroController.changeSeasonOrbangu( + bvid, + cid, + aid, + ), videoIntroCtr: videoIntroController, ), ) ], + // 合集 videoEpisode if (widget.videoDetail!.pages != null && widget.videoDetail!.pages!.length > 1) ...[ Obx( diff --git a/lib/pages/video/detail/introduction/widgets/page_panel.dart b/lib/pages/video/detail/introduction/widgets/page_panel.dart index 730a6105..fc999ba8 100644 --- a/lib/pages/video/detail/introduction/widgets/page_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/page_panel.dart @@ -60,6 +60,7 @@ class _PagesPanelState extends State { } void changeFucCall(item, i) async { + print('pages changeFucCall'); widget.changeFuc?.call(item.cid); currentIndex.value = i; _bottomSheetController?.close(); diff --git a/lib/pages/video/detail/introduction/widgets/season_panel.dart b/lib/pages/video/detail/introduction/widgets/season_panel.dart index cd8bc954..745c081d 100644 --- a/lib/pages/video/detail/introduction/widgets/season_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/season_panel.dart @@ -30,7 +30,7 @@ class SeasonPanel extends StatefulWidget { class _SeasonPanelState extends State { late List episodes; late int cid; - late int currentIndex; + late RxInt currentIndex = (-1).obs; final String heroTag = Get.arguments['heroTag']; late VideoDetailController _videoDetailController; final ItemScrollController itemScrollController = ItemScrollController(); @@ -57,11 +57,10 @@ class _SeasonPanelState extends State { } /// 取对应 season_id 的 episodes - currentIndex = episodes.indexWhere((EpisodeItem e) => e.cid == cid); + currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid); _videoDetailController.cid.listen((int p0) { cid = p0; - setState(() {}); - currentIndex = episodes.indexWhere((EpisodeItem e) => e.cid == cid); + currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid); }); } @@ -71,9 +70,8 @@ class _SeasonPanelState extends State { item.cid, item.aid, ); - currentIndex = i; + currentIndex.value = i; _bottomSheetController?.close(); - setState(() {}); } Widget buildEpisodeListItem( @@ -148,10 +146,10 @@ class _SeasonPanelState extends State { height: 12, ), const SizedBox(width: 10), - Text( - '${currentIndex + 1}/${episodes.length}', - style: Theme.of(context).textTheme.labelMedium, - ), + Obx(() => Text( + '${currentIndex.value + 1}/${episodes.length}', + style: Theme.of(context).textTheme.labelMedium, + )), const SizedBox(width: 6), const Icon( Icons.arrow_forward_ios_outlined, diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index ebbaeec9..7cbc7fed 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -178,6 +178,9 @@ class _VideoDetailPageState extends State if (isFullScreen) { vdCtr.hiddenReplyReplyPanel(); videoIntroController.hiddenEpisodeBottomSheet(); + vdCtr.bottomList.insert(3, BottomControlType.episode); + } else { + vdCtr.bottomList.removeAt(3); } }); } @@ -294,17 +297,20 @@ class _VideoDetailPageState extends State () { return !vdCtr.autoPlay.value ? const SizedBox() - : PLVideoPlayer( - controller: plPlayerController!, - headerControl: vdCtr.headerControl, - danmuWidget: Obx( - () => PlDanmaku( + : Obx( + () => PLVideoPlayer( + controller: plPlayerController!, + headerControl: vdCtr.headerControl, + danmuWidget: PlDanmaku( key: Key(vdCtr.danmakuCid.value.toString()), cid: vdCtr.danmakuCid.value, playerController: plPlayerController!, ), + bottomList: vdCtr.bottomList, + showEposideCb: () => vdCtr.videoType == SearchType.video + ? videoIntroController.showEposideHandler() + : bangumiIntroController.showEposideHandler(), ), - bottomList: vdCtr.bottomList, ); }, ); diff --git a/lib/plugin/pl_player/models/bottom_control_type.dart b/lib/plugin/pl_player/models/bottom_control_type.dart index 739e1d38..d724c5de 100644 --- a/lib/plugin/pl_player/models/bottom_control_type.dart +++ b/lib/plugin/pl_player/models/bottom_control_type.dart @@ -4,6 +4,7 @@ enum BottomControlType { next, time, space, + episode, fit, speed, fullscreen, diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 0e411f2e..6a5f22ec 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -37,6 +37,7 @@ class PLVideoPlayer extends StatefulWidget { this.bottomList, this.customWidget, this.customWidgets, + this.showEposideCb, super.key, }); @@ -49,6 +50,7 @@ class PLVideoPlayer extends StatefulWidget { final Widget? customWidget; final List? customWidgets; + final Function? showEposideCb; @override State createState() => _PLVideoPlayerState(); @@ -267,6 +269,24 @@ class _PLVideoPlayerState extends State /// 空白占位 BottomControlType.space: const Spacer(), + /// 选集 + BottomControlType.episode: SizedBox( + height: 30, + width: 30, + child: TextButton( + onPressed: () { + widget.showEposideCb?.call(); + }, + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + child: const Text( + '选集', + style: TextStyle(color: Colors.white, fontSize: 13), + ), + ), + ), + /// 画面比例 BottomControlType.fit: SizedBox( height: 30, diff --git a/lib/utils/drawer.dart b/lib/utils/drawer.dart new file mode 100644 index 00000000..f4aa17f8 --- /dev/null +++ b/lib/utils/drawer.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +class DrawerUtils { + static void showRightDialog({ + required Widget child, + double width = 400, + bool useSystem = false, + }) { + SmartDialog.show( + alignment: Alignment.topRight, + animationBuilder: (controller, child, animationParam) { + return SlideTransition( + position: Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(controller.view), + child: child, + ); + }, + useSystem: useSystem, + maskColor: Colors.black.withOpacity(0.5), + animationTime: const Duration(milliseconds: 200), + builder: (context) => Container( + width: width, + color: Theme.of(context).scaffoldBackgroundColor, + child: SafeArea( + left: false, + right: false, + bottom: false, + child: MediaQuery( + data: const MediaQueryData(padding: EdgeInsets.zero), + child: child, + ), + ), + ), + ); + } +} From 46c975bbbb5b5b53dd5554617c79de0f823f2b75 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Apr 2024 21:57:57 +0800 Subject: [PATCH 10/18] =?UTF-8?q?mod:=20=E5=90=88=E9=9B=86=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E5=B1=95=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/view.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 7cbc7fed..822d0a45 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -178,9 +178,15 @@ class _VideoDetailPageState extends State if (isFullScreen) { vdCtr.hiddenReplyReplyPanel(); videoIntroController.hiddenEpisodeBottomSheet(); - vdCtr.bottomList.insert(3, BottomControlType.episode); + if (videoIntroController.videoDetail.value.ugcSeason != null || + (videoIntroController.videoDetail.value.pages != null && + videoIntroController.videoDetail.value.pages!.length > 1)) { + vdCtr.bottomList.insert(3, BottomControlType.episode); + } } else { - vdCtr.bottomList.removeAt(3); + if (vdCtr.bottomList.contains(BottomControlType.episode)) { + vdCtr.bottomList.removeAt(3); + } } }); } From 9c12c2179623091028d14bc2823b6e47eb577d1e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 7 Apr 2024 23:13:51 +0800 Subject: [PATCH 11/18] =?UTF-8?q?mod:=20navigation=20Bar=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/setting/pages/navigation_bar_set.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/setting/pages/navigation_bar_set.dart b/lib/pages/setting/pages/navigation_bar_set.dart index 8e1771e3..09e92bc3 100644 --- a/lib/pages/setting/pages/navigation_bar_set.dart +++ b/lib/pages/setting/pages/navigation_bar_set.dart @@ -74,7 +74,7 @@ class _NavigationbarSetPageState extends State { }, title: Text(defaultNavTabs[i]['label']), secondary: const Icon(Icons.drag_indicator_rounded), - enabled: defaultNavTabs[i]['id'] != 3, + enabled: defaultNavTabs[i]['id'] != 0, ) ] ]; From 9ee0f6526c8b7aecb39c9b6df4e7c4d77e25c03b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 7 Apr 2024 23:48:27 +0800 Subject: [PATCH 12/18] =?UTF-8?q?mod:=20=E8=AE=A2=E9=98=85=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E7=B1=BB=E5=9E=8B=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 7 +++- lib/http/user.dart | 33 +++++++++++++++- lib/pages/subscription_detail/controller.dart | 21 ++++++---- lib/pages/subscription_detail/view.dart | 39 +++++++++---------- 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index fa4cc1e8..b6975c4b 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -490,8 +490,11 @@ class Api { /// 我的订阅 static const userSubFolder = '/x/v3/fav/folder/collected/list'; - /// 我的订阅详情 - static const userSubFolderDetail = '/x/space/fav/season/list'; + /// 我的订阅详情 type 21 + static const userSeasonList = '/x/space/fav/season/list'; + + /// 我的订阅详情 type 11 + static const userResourceList = '/x/v3/fav/resource/list'; /// 表情 static const emojiList = '/x/emote/user/panel/web'; diff --git a/lib/http/user.dart b/lib/http/user.dart index bae61720..fea0a22e 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -330,12 +330,12 @@ class UserHttp { } } - static Future userSubFolderDetail({ + static Future userSeasonList({ required int seasonId, required int pn, required int ps, }) async { - var res = await Request().get(Api.userSubFolderDetail, data: { + var res = await Request().get(Api.userSeasonList, data: { 'season_id': seasonId, 'ps': ps, 'pn': pn, @@ -350,6 +350,35 @@ class UserHttp { } } + static Future userResourceList({ + required int seasonId, + required int pn, + required int ps, + }) async { + var res = await Request().get(Api.userResourceList, data: { + 'media_id': seasonId, + 'ps': ps, + 'pn': pn, + 'keyword': '', + 'order': 'mtime', + 'type': 0, + 'tid': 0, + 'platform': 'web', + }); + if (res.data['code'] == 0) { + try { + return { + 'status': true, + 'data': SubDetailModelData.fromJson(res.data['data']) + }; + } catch (err) { + return {'status': false, 'msg': err}; + } + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + // 取消订阅 static Future cancelSub({required int seasonId}) async { var res = await Request().post( diff --git a/lib/pages/subscription_detail/controller.dart b/lib/pages/subscription_detail/controller.dart index 6ecb894e..4245df2c 100644 --- a/lib/pages/subscription_detail/controller.dart +++ b/lib/pages/subscription_detail/controller.dart @@ -6,7 +6,6 @@ import '../../models/user/sub_folder.dart'; class SubDetailController extends GetxController { late SubFolderItemData item; - late int seasonId; late String heroTag; int currentPage = 1; @@ -26,17 +25,23 @@ class SubDetailController extends GetxController { super.onInit(); } - Future queryUserSubFolderDetail({type = 'init'}) async { + Future queryUserSeasonList({type = 'init'}) async { if (type == 'onLoad' && subList.length >= mediaCount) { loadingText.value = '没有更多了'; return; } isLoadingMore = true; - var res = await UserHttp.userSubFolderDetail( - seasonId: seasonId, - ps: 20, - pn: currentPage, - ); + var res = type == 21 + ? await UserHttp.userSeasonList( + seasonId: seasonId, + ps: 20, + pn: currentPage, + ) + : await UserHttp.userResourceList( + seasonId: seasonId, + ps: 20, + pn: currentPage, + ); if (res['status']) { subInfo.value = res['data'].info; if (currentPage == 1 && type == 'init') { @@ -55,6 +60,6 @@ class SubDetailController extends GetxController { } onLoad() { - queryUserSubFolderDetail(type: 'onLoad'); + queryUserSeasonList(type: 'onLoad'); } } diff --git a/lib/pages/subscription_detail/view.dart b/lib/pages/subscription_detail/view.dart index d56125cd..93e0abbb 100644 --- a/lib/pages/subscription_detail/view.dart +++ b/lib/pages/subscription_detail/view.dart @@ -26,13 +26,11 @@ class _SubDetailPageState extends State { Get.put(SubDetailController()); late StreamController titleStreamC; // a late Future _futureBuilderFuture; - late String seasonId; @override void initState() { super.initState(); - seasonId = Get.parameters['seasonId']!; - _futureBuilderFuture = _subDetailController.queryUserSubFolderDetail(); + _futureBuilderFuture = _subDetailController.queryUserSeasonList(); titleStreamC = StreamController(); _controller.addListener( () { @@ -161,15 +159,18 @@ class _SubDetailPageState extends State { ), ), const SizedBox(height: 4), - Text( - '${Utils.numFormat(_subDetailController.item.viewCount)}次播放', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: Theme.of(context).colorScheme.outline), - ), + Obx( + () => Text( + '${Utils.numFormat(_subDetailController.subInfo.value.cntInfo?['play'])}次播放', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: + Theme.of(context).colorScheme.outline), + ), + ) ], ), ), @@ -182,14 +183,12 @@ class _SubDetailPageState extends State { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14), - child: Obx( - () => Text( - '共${_subDetailController.subList.length}条视频', - style: TextStyle( - fontSize: - Theme.of(context).textTheme.labelMedium!.fontSize, - color: Theme.of(context).colorScheme.outline, - letterSpacing: 1), + child: Text( + '共${_subDetailController.item.mediaCount}条视频', + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + letterSpacing: 1, ), ), ), From 2bd97f800efb4e88dda04e307739cefb2dcc9b58 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 8 Apr 2024 23:43:25 +0800 Subject: [PATCH 13/18] Update beta_ci.yml --- .github/workflows/beta_ci.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/beta_ci.yml b/.github/workflows/beta_ci.yml index e839aca1..80fe8afb 100644 --- a/.github/workflows/beta_ci.yml +++ b/.github/workflows/beta_ci.yml @@ -1,17 +1,5 @@ name: Pilipala Beta -on: - workflow_dispatch: - push: - branches: - - "main" - paths-ignore: - - "**.md" - - "**.txt" - - ".github/**" - - ".idea/**" - - "!.github/workflows/**" - jobs: update_version: name: Read and update version From b9e93dabe62884251fd617719e3b647406b97097 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 8 Apr 2024 23:45:54 +0800 Subject: [PATCH 14/18] Update beta_ci.yml --- .github/workflows/beta_ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/beta_ci.yml b/.github/workflows/beta_ci.yml index 80fe8afb..40f3f042 100644 --- a/.github/workflows/beta_ci.yml +++ b/.github/workflows/beta_ci.yml @@ -1,5 +1,18 @@ name: Pilipala Beta +on: + workflow_dispatch: + push: + branches: + - "never" + paths-ignore: + - "**.md" + - "**.txt" + - ".github/**" + - ".idea/**" + - "!.github/workflows/**" + + jobs: update_version: name: Read and update version From 84f83c260ab092e9040093bf894b9d9d035192c9 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 8 Apr 2024 23:55:29 +0800 Subject: [PATCH 15/18] =?UTF-8?q?feat:=20=E7=AE=80=E5=8D=95=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=8A=95=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/dlna/index.dart | 111 ++++++++++++++++++ .../video/detail/widgets/header_control.dart | 17 +++ pubspec.lock | 8 ++ pubspec.yaml | 2 + 4 files changed, 138 insertions(+) create mode 100644 lib/pages/dlna/index.dart diff --git a/lib/pages/dlna/index.dart b/lib/pages/dlna/index.dart new file mode 100644 index 00000000..3ec5965b --- /dev/null +++ b/lib/pages/dlna/index.dart @@ -0,0 +1,111 @@ +import 'dart:async'; + +import 'package:dlna_dart/dlna.dart'; +import 'package:flutter/material.dart'; + +class LiveDlnaPage extends StatefulWidget { + final String datasource; + + const LiveDlnaPage({Key? key, required this.datasource}) : super(key: key); + + @override + State createState() => _LiveDlnaPageState(); +} + +class _LiveDlnaPageState extends State { + final Map _deviceList = {}; + final DLNAManager searcher = DLNAManager(); + late final Timer stopSearchTimer; + String selectDeviceKey = ''; + bool isSearching = true; + + DLNADevice? get device => _deviceList[selectDeviceKey]; + + @override + void initState() { + stopSearchTimer = Timer(const Duration(seconds: 20), () { + setState(() => isSearching = false); + searcher.stop(); + }); + searcher.stop(); + startSearch(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + searcher.stop(); + stopSearchTimer.cancel(); + } + + void startSearch() async { + // clear old devices + isSearching = true; + selectDeviceKey = ''; + _deviceList.clear(); + setState(() {}); + // start search server + final m = await searcher.start(); + m.devices.stream.listen((deviceList) { + deviceList.forEach((key, value) { + _deviceList[key] = value; + }); + setState(() {}); + }); + // close the server, the closed server can be start by call searcher.start() + } + + void selectDevice(String key) { + if (selectDeviceKey.isNotEmpty) device?.pause(); + + selectDeviceKey = key; + device?.setUrl(widget.datasource); + device?.play(); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + Widget cur; + if (isSearching && _deviceList.isEmpty) { + cur = const Center(child: CircularProgressIndicator()); + } else if (_deviceList.isEmpty) { + cur = Center( + child: Text( + '没有找到设备', + style: Theme.of(context).textTheme.bodyLarge, + ), + ); + } else { + cur = ListView( + children: _deviceList.keys + .map((key) => ListTile( + contentPadding: const EdgeInsets.all(2), + title: Text(_deviceList[key]!.info.friendlyName), + subtitle: Text(key), + onTap: () => selectDevice(key), + )) + .toList(), + ); + } + + return AlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('查找设备'), + IconButton( + onPressed: startSearch, + icon: const Icon(Icons.refresh_rounded), + ), + ], + ), + content: SizedBox( + height: 200, + width: 200, + child: cur, + ), + ); + } +} diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 4a8f4759..7a12b0cf 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -11,6 +11,7 @@ import 'package:ns_danmaku/ns_danmaku.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/url.dart'; +import 'package:pilipala/pages/dlna/index.dart'; import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart'; import 'package:pilipala/plugin/pl_player/index.dart'; @@ -1209,6 +1210,22 @@ class _HeaderControlState extends State { // ), // fuc: () => _.screenshot(), // ), + ComBtn( + icon: const Icon( + Icons.cast, + size: 19, + color: Colors.white, + ), + fuc: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return LiveDlnaPage( + datasource: widget.videoDetailCtr!.videoUrl); + }, + ); + }, + ), if (isFullScreen.value) ...[ SizedBox( width: 56, diff --git a/pubspec.lock b/pubspec.lock index 84556c06..a64c85c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -409,6 +409,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" + dlna_dart: + dependency: "direct main" + description: + name: dlna_dart + sha256: ae07c1c53077bbf58756fa589f936968719b0085441981d33e74f82f89d1d281 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.8" dynamic_color: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ba5976eb..27b8b720 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -144,6 +144,8 @@ dependencies: disable_battery_optimization: ^1.1.1 # 展开/收起 expandable: ^5.0.1 + # 投屏 + dlna_dart: ^0.0.8 dev_dependencies: flutter_test: From ca37d45eb94007be0fa666ab993fcf3a0049fe7b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 9 Apr 2024 23:22:57 +0800 Subject: [PATCH 16/18] =?UTF-8?q?fix:=20=E5=85=A8=E5=B1=8F=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=90=88=E9=9B=86=E8=A7=86=E9=A2=91=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E6=9C=AA=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../video/detail/widgets/header_control.dart | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 7a12b0cf..e6a324cb 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -83,7 +83,6 @@ class _HeaderControlState extends State { /// 设置面板 void showSettingSheet() { - // Scaffold.of(context).openDrawer(); showModalBottomSheet( elevation: 0, context: context, @@ -732,9 +731,12 @@ class _HeaderControlState extends State { margin: const EdgeInsets.all(12), child: Column( children: [ - SizedBox( - height: 45, - child: Center(child: Text('选择解码格式', style: titleStyle))), + const SizedBox( + height: 45, + child: Center( + child: Text('选择解码格式', style: titleStyle), + ), + ), Expanded( child: Material( child: ListView( @@ -1079,9 +1081,12 @@ class _HeaderControlState extends State { margin: const EdgeInsets.all(12), child: Column( children: [ - SizedBox( - height: 45, - child: Center(child: Text('选择播放顺序', style: titleStyle))), + const SizedBox( + height: 45, + child: Center( + child: Text('选择播放顺序', style: titleStyle), + ), + ), Expanded( child: Material( child: ListView( @@ -1166,11 +1171,13 @@ class _HeaderControlState extends State { children: [ ConstrainedBox( constraints: const BoxConstraints(maxWidth: 200), - child: Text( - videoIntroController.videoDetail.value.title ?? '', - style: const TextStyle( - color: Colors.white, - fontSize: 16, + child: Obx( + () => Text( + videoIntroController.videoDetail.value.title ?? '', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), ), ), ), From 04bfc294525d47511ea9c005fbcfa2c9addae142 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 9 Apr 2024 23:33:13 +0800 Subject: [PATCH 17/18] =?UTF-8?q?feat:=20=E7=9B=B4=E6=92=AD=E9=97=B4?= =?UTF-8?q?=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/live_room/view.dart | 5 ++++ .../live_room/widgets/bottom_control.dart | 28 +++++++------------ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 1e5c29c5..37981b1d 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -62,6 +62,11 @@ class _LiveRoomPageState extends State { controller: plPlayerController, liveRoomCtr: _liveRoomController, floating: floating, + onRefresh: () { + setState(() { + _futureBuilderFuture = _liveRoomController.queryLiveInfo(); + }); + }, ), ); } else { diff --git a/lib/pages/live_room/widgets/bottom_control.dart b/lib/pages/live_room/widgets/bottom_control.dart index 3c908d71..e5a9d6c9 100644 --- a/lib/pages/live_room/widgets/bottom_control.dart +++ b/lib/pages/live_room/widgets/bottom_control.dart @@ -14,10 +14,12 @@ class BottomControl extends StatefulWidget implements PreferredSizeWidget { final PlPlayerController? controller; final LiveRoomController? liveRoomCtr; final Floating? floating; + final Function? onRefresh; const BottomControl({ this.controller, this.liveRoomCtr, this.floating, + this.onRefresh, Key? key, }) : super(key: key); @@ -61,6 +63,14 @@ class _BottomControlState extends State { // ), // fuc: () => Get.back(), // ), + ComBtn( + icon: const Icon( + Icons.refresh_outlined, + size: 18, + color: Colors.white, + ), + fuc: widget.onRefresh, + ), const Spacer(), // ComBtn( // icon: const Icon( @@ -150,21 +160,3 @@ class _BottomControlState extends State { ); } } - -class MSliderTrackShape extends RoundedRectSliderTrackShape { - @override - Rect getPreferredRect({ - required RenderBox parentBox, - Offset offset = Offset.zero, - SliderThemeData? sliderTheme, - bool isEnabled = false, - bool isDiscrete = false, - }) { - const double trackHeight = 3; - final double trackLeft = offset.dx; - final double trackTop = - offset.dy + (parentBox.size.height - trackHeight) / 2 + 4; - final double trackWidth = parentBox.size.width; - return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight); - } -} From 9f9471d7f9961332077b98380c5f8afa096e94cc Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 9 Apr 2024 23:42:09 +0800 Subject: [PATCH 18/18] =?UTF-8?q?fix:=20up=E4=B8=BB=E9=A1=B5=E4=B8=93?= =?UTF-8?q?=E6=A0=8F=E8=A7=86=E9=A2=91=E6=97=B6=E9=95=BF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member_seasons/widgets/item.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pages/member_seasons/widgets/item.dart b/lib/pages/member_seasons/widgets/item.dart index 6398c5eb..4df74b70 100644 --- a/lib/pages/member_seasons/widgets/item.dart +++ b/lib/pages/member_seasons/widgets/item.dart @@ -25,7 +25,7 @@ class MemberSeasonsItem extends StatelessWidget { child: InkWell( onTap: () async { int cid = - await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid); + await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid); Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid', arguments: {'videoItem': seasonItem, 'heroTag': heroTag}); }, @@ -51,8 +51,7 @@ class MemberSeasonsItem extends StatelessWidget { bottom: 6, right: 6, type: 'gray', - text: Utils.CustomStamp_str( - timestamp: seasonItem.pubdate, date: 'YY-MM-DD'), + text: Utils.timeFormat(seasonItem.duration), ) ], );