From ab10223eca6e0936ad6835dab86cdc6ffbef47b2 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 11 Mar 2024 23:03:50 +0800 Subject: [PATCH 01/34] =?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/34] =?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 8f9fbf5d41c7daa1ed17e774e1710236218a9c23 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 29 Mar 2024 00:01:17 +0800 Subject: [PATCH 03/34] =?UTF-8?q?fix:=20=E8=A7=86=E9=A2=91=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 13 ++-- lib/pages/video/detail/introduction/view.dart | 62 ++++++++++++++----- pubspec.lock | 8 +++ pubspec.yaml | 2 + 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8db59815..2c1a635b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -9,6 +9,7 @@ PODS: - Flutter - connectivity_plus (0.0.1): - Flutter + - FlutterMacOS - ReachabilitySwift - device_info_plus (0.0.1): - Flutter @@ -38,7 +39,7 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.1.1): + - permission_handler_apple (9.3.0): - Flutter - ReachabilitySwift (5.0.0) - saver_gallery (0.0.1): @@ -71,7 +72,7 @@ DEPENDENCIES: - audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`) - auto_orientation (from `.symlinks/plugins/auto_orientation/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) @@ -113,7 +114,7 @@ EXTERNAL SOURCES: auto_orientation: :path: ".symlinks/plugins/auto_orientation/ios" connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" + :path: ".symlinks/plugins/connectivity_plus/darwin" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: @@ -166,7 +167,7 @@ SPEC CHECKSUMS: audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_session: 4f3e461722055d21515cf3261b64c973c062f345 auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d - connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a + connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 @@ -180,7 +181,7 @@ SPEC CHECKSUMS: media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 @@ -193,7 +194,7 @@ SPEC CHECKSUMS: volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7 - webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a + webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4 PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 344b4b3a..a990aab8 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,3 +1,4 @@ +import 'package:expandable/expandable.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -16,7 +17,6 @@ import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; import '../../../../http/user.dart'; -import '../widgets/expandable_section.dart'; import 'widgets/action_item.dart'; import 'widgets/fav_panel.dart'; import 'widgets/intro_detail.dart'; @@ -140,6 +140,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { late bool enableAi; bool isProcessing = false; RxBool isExpand = false.obs; + late ExpandableController _expandableCtr; + void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -163,6 +165,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { follower = Utils.numFormat(videoIntroController.userStat['follower']); followStatus = videoIntroController.followStatus; enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true); + _expandableCtr = ExpandableController(initialExpanded: false); } // 收藏 @@ -216,6 +219,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { showIntroDetail() { feedBack(); isExpand.value = !(isExpand.value); + _expandableCtr.toggle(); } // 用户主页 @@ -239,6 +243,12 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ); } + @override + void dispose() { + _expandableCtr.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final ThemeData t = Theme.of(context); @@ -256,14 +266,34 @@ class _VideoInfoState extends State with TickerProviderStateMixin { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => showIntroDetail(), - child: Text( - widget.videoDetail!.title!, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: ExpandablePanel( + controller: _expandableCtr, + collapsed: Text( + widget.videoDetail!.title!, + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + expanded: Text( + widget.videoDetail!.title!, + softWrap: true, + maxLines: 4, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + theme: const ExpandableThemeData( + animationDuration: Duration(milliseconds: 300), + scrollAnimationDuration: Duration(milliseconds: 300), + crossFadePoint: 0, + fadeCurve: Curves.ease, + sizeCurve: Curves.linear, ), - maxLines: 2, - overflow: TextOverflow.ellipsis, ), ), Stack( @@ -328,12 +358,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), /// 视频简介 - Obx( - () => ExpandedSection( - expand: isExpand.value, - begin: 0, - end: 1, - child: IntroDetail(videoDetail: widget.videoDetail!), + ExpandablePanel( + controller: _expandableCtr, + collapsed: const SizedBox(height: 0), + expanded: IntroDetail(videoDetail: widget.videoDetail!), + theme: const ExpandableThemeData( + animationDuration: Duration(milliseconds: 300), + scrollAnimationDuration: Duration(milliseconds: 300), + crossFadePoint: 0, + fadeCurve: Curves.ease, + sizeCurve: Curves.linear, ), ), diff --git a/pubspec.lock b/pubspec.lock index 695505d7..84556c06 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,6 +433,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.3" + expandable: + dependency: "direct main" + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.1" extended_image: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5e19b56b..ba5976eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -142,6 +142,8 @@ dependencies: path: 1.8.3 # 电池优化 disable_battery_optimization: ^1.1.1 + # 展开/收起 + expandable: ^5.0.1 dev_dependencies: flutter_test: From d806de7d8f982fe0ea6a650692a63408d853bd8d Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 28 Mar 2024 00:00:27 +0800 Subject: [PATCH 04/34] =?UTF-8?q?feat:=20=E6=92=AD=E6=94=BE=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E8=BF=9B=E5=BA=A6=E6=9D=A1=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/history/widgets/item.dart | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index a83e118b..f4bd9221 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -185,7 +185,7 @@ class HistoryItem extends StatelessWidget { ? '已看完' : '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}', right: 6.0, - bottom: 6.0, + bottom: 8.0, type: 'gray', ), // 右上角 @@ -258,6 +258,24 @@ class HistoryItem extends StatelessWidget { ), ), ), + Positioned( + left: 3, + right: 3, + bottom: 0, + child: ClipRRect( + borderRadius: BorderRadius.only( + bottomLeft: + Radius.circular(StyleString.imgRadius.x), + bottomRight: + Radius.circular(StyleString.imgRadius.x), + ), + child: LinearProgressIndicator( + value: videoItem.progress == -1 + ? 100 + : videoItem.progress / videoItem.duration, + ), + ), + ) ], ), VideoContent(videoItem: videoItem, ctr: ctr) From 6c2eab86e949c8c518fba3acde0456f60eb3b535 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 29 Mar 2024 00:01:17 +0800 Subject: [PATCH 05/34] =?UTF-8?q?fix:=20=E8=A7=86=E9=A2=91=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 62 ++++++++++++++----- pubspec.lock | 8 +++ pubspec.yaml | 2 + 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 344b4b3a..a990aab8 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,3 +1,4 @@ +import 'package:expandable/expandable.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -16,7 +17,6 @@ import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; import '../../../../http/user.dart'; -import '../widgets/expandable_section.dart'; import 'widgets/action_item.dart'; import 'widgets/fav_panel.dart'; import 'widgets/intro_detail.dart'; @@ -140,6 +140,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { late bool enableAi; bool isProcessing = false; RxBool isExpand = false.obs; + late ExpandableController _expandableCtr; + void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -163,6 +165,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { follower = Utils.numFormat(videoIntroController.userStat['follower']); followStatus = videoIntroController.followStatus; enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true); + _expandableCtr = ExpandableController(initialExpanded: false); } // 收藏 @@ -216,6 +219,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { showIntroDetail() { feedBack(); isExpand.value = !(isExpand.value); + _expandableCtr.toggle(); } // 用户主页 @@ -239,6 +243,12 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ); } + @override + void dispose() { + _expandableCtr.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final ThemeData t = Theme.of(context); @@ -256,14 +266,34 @@ class _VideoInfoState extends State with TickerProviderStateMixin { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => showIntroDetail(), - child: Text( - widget.videoDetail!.title!, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + child: ExpandablePanel( + controller: _expandableCtr, + collapsed: Text( + widget.videoDetail!.title!, + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + expanded: Text( + widget.videoDetail!.title!, + softWrap: true, + maxLines: 4, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + theme: const ExpandableThemeData( + animationDuration: Duration(milliseconds: 300), + scrollAnimationDuration: Duration(milliseconds: 300), + crossFadePoint: 0, + fadeCurve: Curves.ease, + sizeCurve: Curves.linear, ), - maxLines: 2, - overflow: TextOverflow.ellipsis, ), ), Stack( @@ -328,12 +358,16 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ), /// 视频简介 - Obx( - () => ExpandedSection( - expand: isExpand.value, - begin: 0, - end: 1, - child: IntroDetail(videoDetail: widget.videoDetail!), + ExpandablePanel( + controller: _expandableCtr, + collapsed: const SizedBox(height: 0), + expanded: IntroDetail(videoDetail: widget.videoDetail!), + theme: const ExpandableThemeData( + animationDuration: Duration(milliseconds: 300), + scrollAnimationDuration: Duration(milliseconds: 300), + crossFadePoint: 0, + fadeCurve: Curves.ease, + sizeCurve: Curves.linear, ), ), diff --git a/pubspec.lock b/pubspec.lock index 695505d7..84556c06 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,6 +433,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "5.0.3" + expandable: + dependency: "direct main" + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.1" extended_image: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5e19b56b..ba5976eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -142,6 +142,8 @@ dependencies: path: 1.8.3 # 电池优化 disable_battery_optimization: ^1.1.1 + # 展开/收起 + expandable: ^5.0.1 dev_dependencies: flutter_test: From d003f864cec8076bb97fc63b99df8eee139ad06c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 30 Mar 2024 17:01:32 +0800 Subject: [PATCH 06/34] =?UTF-8?q?feat:=20=E8=AE=A2=E9=98=85=E5=8F=96?= =?UTF-8?q?=E6=B6=88=20issues=20#658?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 2 ++ lib/http/user.dart | 17 +++++++++++ lib/pages/subscription/controller.dart | 36 ++++++++++++++++++++++++ lib/pages/subscription/view.dart | 3 +- lib/pages/subscription/widgets/item.dart | 33 ++++++++++++++++++++-- 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 445f6102..fa4cc1e8 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -506,4 +506,6 @@ class Api { /// 排行榜 static const String getRankApi = "/x/web-interface/ranking/v2"; + /// 取消订阅 + static const String cancelSub = '/x/v3/fav/season/unfav'; } diff --git a/lib/http/user.dart b/lib/http/user.dart index 7d3def4e..bae61720 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -349,4 +349,21 @@ class UserHttp { return {'status': false, 'msg': res.data['message']}; } } + + // 取消订阅 + static Future cancelSub({required int seasonId}) async { + var res = await Request().post( + Api.cancelSub, + queryParameters: { + 'platform': 'web', + 'season_id': seasonId, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return {'status': true}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } } diff --git a/lib/pages/subscription/controller.dart b/lib/pages/subscription/controller.dart index bf0c593c..7be8d22c 100644 --- a/lib/pages/subscription/controller.dart +++ b/lib/pages/subscription/controller.dart @@ -46,4 +46,40 @@ class SubController extends GetxController { Future onLoad() async { querySubFolder(type: 'onload'); } + + // 取消订阅 + Future cancelSub(SubFolderItemData subFolderItem) async { + showDialog( + context: Get.context!, + builder: (context) => AlertDialog( + title: const Text('提示'), + content: const Text('确定取消订阅吗?'), + actions: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () async { + var res = await UserHttp.cancelSub(seasonId: subFolderItem.id!); + if (res['status']) { + subFolderData.value.list!.remove(subFolderItem); + subFolderData.update((val) {}); + SmartDialog.showToast('取消订阅成功'); + } else { + SmartDialog.showToast(res['msg']); + } + Get.back(); + }, + child: const Text('确定'), + ), + ], + ), + ); + } } diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 1eee4a4f..2d7d0cb5 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -58,7 +58,8 @@ class _SubPageState extends State { itemBuilder: (context, index) { return SubItem( subFolderItem: - _subController.subFolderData.value.list![index]); + _subController.subFolderData.value.list![index], + cancelSub: _subController.cancelSub); }, ), ); diff --git a/lib/pages/subscription/widgets/item.dart b/lib/pages/subscription/widgets/item.dart index fd08ffa5..5b2a0134 100644 --- a/lib/pages/subscription/widgets/item.dart +++ b/lib/pages/subscription/widgets/item.dart @@ -8,7 +8,12 @@ import '../../../models/user/sub_folder.dart'; class SubItem extends StatelessWidget { final SubFolderItemData subFolderItem; - const SubItem({super.key, required this.subFolderItem}); + final Function(SubFolderItemData) cancelSub; + const SubItem({ + super.key, + required this.subFolderItem, + required this.cancelSub, + }); @override Widget build(BuildContext context) { @@ -51,7 +56,10 @@ class SubItem extends StatelessWidget { }, ), ), - VideoContent(subFolderItem: subFolderItem) + VideoContent( + subFolderItem: subFolderItem, + cancelSub: cancelSub, + ) ], ), ); @@ -64,7 +72,8 @@ class SubItem extends StatelessWidget { class VideoContent extends StatelessWidget { final SubFolderItemData subFolderItem; - const VideoContent({super.key, required this.subFolderItem}); + final Function(SubFolderItemData)? cancelSub; + const VideoContent({super.key, required this.subFolderItem, this.cancelSub}); @override Widget build(BuildContext context) { @@ -100,6 +109,24 @@ class VideoContent extends StatelessWidget { color: Theme.of(context).colorScheme.outline, ), ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 35, + width: 35, + child: IconButton( + onPressed: () => cancelSub?.call(subFolderItem), + style: TextButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.outline, + padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), + ), + icon: const Icon(Icons.delete_outline, size: 18), + ), + ) + ], + ) ], ), ), From af1163f6e05193e6023b217e8b85fcbacc942721 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 30 Mar 2024 22:17:37 +0800 Subject: [PATCH 07/34] =?UTF-8?q?fix:=20=E5=8E=86=E5=8F=B2=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E8=BF=9B=E5=BA=A6=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/history/widgets/item.dart | 39 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index f4bd9221..39c6931d 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -258,24 +258,27 @@ class HistoryItem extends StatelessWidget { ), ), ), - Positioned( - left: 3, - right: 3, - bottom: 0, - child: ClipRRect( - borderRadius: BorderRadius.only( - bottomLeft: - Radius.circular(StyleString.imgRadius.x), - bottomRight: - Radius.circular(StyleString.imgRadius.x), - ), - child: LinearProgressIndicator( - value: videoItem.progress == -1 - ? 100 - : videoItem.progress / videoItem.duration, - ), - ), - ) + videoItem.progress != 0 + ? Positioned( + left: 3, + right: 3, + bottom: 0, + child: ClipRRect( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular( + StyleString.imgRadius.x), + bottomRight: Radius.circular( + StyleString.imgRadius.x), + ), + child: LinearProgressIndicator( + value: videoItem.progress == -1 + ? 100 + : videoItem.progress / + videoItem.duration, + ), + ), + ) + : const SizedBox() ], ), VideoContent(videoItem: videoItem, ctr: ctr) From 53b103b8535b1938569a55c8f460815404cccdbb Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 31 Mar 2024 00:27:39 +0800 Subject: [PATCH 08/34] fix: utils timeFormat error --- lib/utils/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index adcc7b5a..ecba771f 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -51,7 +51,7 @@ class Utils { } if (time < 3600) { if (time == 0) { - return time; + return '00:00'; } final int minute = time ~/ 60; final double res = time / 60; From 336feb4fda028b7ae7ea022af4628ce3b5d4adbe Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 31 Mar 2024 00:27:39 +0800 Subject: [PATCH 09/34] fix: utils timeFormat error --- lib/utils/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index adcc7b5a..ecba771f 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -51,7 +51,7 @@ class Utils { } if (time < 3600) { if (time == 0) { - return time; + return '00:00'; } final int minute = time ~/ 60; final double res = time / 60; From a20217bf3962d309b4d2a19018a01769f5734f57 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 28 Mar 2024 00:29:22 +0800 Subject: [PATCH 10/34] =?UTF-8?q?opt:=20=E5=90=91=E4=B8=8B=E6=9F=A5?= =?UTF-8?q?=E6=89=BE=E5=8F=AF=E7=94=A8=E8=A7=86=E9=A2=91=E6=B8=85=E6=99=B0?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/utils.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index ecba771f..cb7cbf25 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -208,14 +208,16 @@ class Utils { static int findClosestNumber(int target, List numbers) { int minDiff = 127; - late int closestNumber; + int closestNumber = 0; // 初始化为0,表示没有找到比目标值小的整数 try { for (int number in numbers) { - int diff = (number - target).abs(); + if (number < target) { + int diff = target - number; // 计算目标值与当前整数的差值 - if (diff < minDiff) { - minDiff = diff; - closestNumber = number; + if (diff < minDiff) { + minDiff = diff; + closestNumber = number; + } } } } catch (_) {} From e212a327635ba608a148bdfacbbe9df9e33e11de Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 31 Mar 2024 11:11:50 +0800 Subject: [PATCH 11/34] =?UTF-8?q?feat:=20=E5=90=AF=E5=8A=A8=E6=97=B6?= =?UTF-8?q?=E6=B8=85=E9=99=A4=E6=97=A5=E5=BF=97=20issues=20#656?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main.dart b/lib/main.dart index 44bb1dcd..7fdaeeb0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,6 +34,7 @@ void main() async { .then((_) async { await GStrorage.init(); await setupServiceLocator(); + clearLogs(); Request(); await Request.setCookie(); RecommendFilter(); From da9828a295b51f267be2464f03f070ed7968b4da Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 1 Apr 2024 23:03:31 +0800 Subject: [PATCH 12/34] =?UTF-8?q?mod:=20=E4=B8=AA=E4=BA=BA=E4=B8=BB?= =?UTF-8?q?=E9=A1=B5=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/member/info.dart | 5 +++ lib/pages/member/view.dart | 13 +++++-- lib/pages/member/widgets/seasons.dart | 53 ++++++++++----------------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/lib/models/member/info.dart b/lib/models/member/info.dart index 789131ee..83f94c54 100644 --- a/lib/models/member/info.dart +++ b/lib/models/member/info.dart @@ -47,18 +47,23 @@ class Vip { this.status, this.dueDate, this.label, + this.nicknameColor, }); int? type; int? status; int? dueDate; Map? label; + int? nicknameColor; Vip.fromJson(Map json) { type = json['type']; status = json['status']; dueDate = json['due_date']; label = json['label']; + nicknameColor = json['nickname_color'] == '' + ? null + : int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}"); } } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 0663e94e..c8a9f406 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -281,8 +281,8 @@ class _MemberPageState extends State future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data!; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { return Obx( () => Stack( alignment: AlignmentDirectional.center, @@ -302,7 +302,14 @@ class _MemberPageState extends State style: Theme.of(context) .textTheme .titleMedium! - .copyWith(fontWeight: FontWeight.bold), + .copyWith( + fontWeight: FontWeight.bold, + color: _memberController.memberInfo.value + .vip!.nicknameColor != + null + ? Color(_memberController.memberInfo + .value.vip!.nicknameColor!) + : null), )), const SizedBox(width: 2), if (_memberController.memberInfo.value.sex == '女') diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 68c4077f..125c978f 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -18,45 +18,32 @@ class MemberSeasonsPanel extends StatelessWidget { itemBuilder: (context, index) { MemberSeasonsList item = data!.seasonsList![index]; return Padding( - padding: const EdgeInsets.only(bottom: 12, right: 4), + padding: const EdgeInsets.only(bottom: 12), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(bottom: 12, left: 4), - child: Row( - children: [ - Text( - item.meta!.name!, - maxLines: 1, - style: Theme.of(context).textTheme.titleSmall!, - ), - const SizedBox(width: 10), - PBadge( - stack: 'relative', - size: 'small', - text: item.meta!.total.toString(), - ), - const Spacer(), - SizedBox( - width: 35, - height: 35, - child: IconButton( - onPressed: () => Get.toNamed( - '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'), - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - icon: const Icon( - Icons.arrow_forward, - size: 20, - ), - ), - ) - ], + ListTile( + onTap: () => Get.toNamed( + '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'), + title: Text( + item.meta!.name!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleSmall!, + ), + dense: true, + leading: PBadge( + stack: 'relative', + size: 'small', + text: item.meta!.total.toString(), + ), + trailing: const Icon( + Icons.arrow_forward, + size: 20, ), ), + const SizedBox(height: 10), LayoutBuilder( builder: (context, boxConstraints) { return GridView.builder( From c6de1fa95a80a63734f0a1b39b46555557796208 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 1 Apr 2024 23:55:35 +0800 Subject: [PATCH 13/34] =?UTF-8?q?mod:=20=E8=AF=84=E8=AE=BAb23.tv=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E5=8C=B9=E9=85=8D=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/reply/widgets/reply_item.dart | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index e79b6159..50fe20d4 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -525,14 +525,18 @@ InlineSpan buildContent( if (jumpUrlKeysList.isNotEmpty) { patternStr += '|${jumpUrlKeysList.join('|')}'; } + RegExp bv23Regex = RegExp(r'https://b23\.tv/[a-zA-Z0-9]{7}'); final RegExp pattern = RegExp(patternStr); List matchedStrs = []; void addPlainTextSpan(str) { - spanChilds.add(TextSpan( + spanChilds.add( + TextSpan( text: str, recognizer: TapGestureRecognizer() ..onTap = () => - replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem))); + replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem), + ), + ); } // 分割文本并处理每个部分 @@ -734,8 +738,36 @@ InlineSpan buildContent( return ''; }, onNonMatch: (String nonMatchStr) { - addPlainTextSpan(nonMatchStr); - return nonMatchStr; + return nonMatchStr.splitMapJoin( + bv23Regex, + onMatch: (Match match) { + String matchStr = match[0]!; + spanChilds.add( + TextSpan( + text: ' $matchStr ', + style: isVideoPage + ? TextStyle( + color: Theme.of(context).colorScheme.primary, + ) + : null, + recognizer: TapGestureRecognizer() + ..onTap = () => Get.toNamed( + '/webview', + parameters: { + 'url': matchStr, + 'type': 'url', + 'pageTitle': matchStr + }, + ), + ), + ); + return ''; + }, + onNonMatch: (String nonMatchOtherStr) { + addPlainTextSpan(nonMatchOtherStr); + return nonMatchOtherStr; + }, + ); }, ); From c0f3b4f3a2052eb6b4aa4f73022f6f884d432d3e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 2 Apr 2024 23:13:15 +0800 Subject: [PATCH 14/34] =?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 74f6b0ad1e3aaf0d80b917c68fddb0f853755dc0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 2 Apr 2024 23:58:27 +0800 Subject: [PATCH 15/34] =?UTF-8?q?fix:=20=E8=AF=84=E8=AE=BA=E6=A1=86?= =?UTF-8?q?=E5=88=87=E6=8D=A2action=E6=97=B6=E9=AB=98=E5=BA=A6=E8=B7=B3?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/reply_new/view.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pages/video/detail/reply_new/view.dart b/lib/pages/video/detail/reply_new/view.dart index 05351411..a94b6071 100644 --- a/lib/pages/video/detail/reply_new/view.dart +++ b/lib/pages/video/detail/reply_new/view.dart @@ -142,9 +142,10 @@ class _VideoReplyNewDialogState extends State @override Widget build(BuildContext context) { - double keyboardHeight = EdgeInsets.fromViewPadding( + double _keyboardHeight = EdgeInsets.fromViewPadding( View.of(context).viewInsets, View.of(context).devicePixelRatio) .bottom; + print('_keyboardHeight: $_keyboardHeight'); return Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( @@ -235,7 +236,11 @@ class _VideoReplyNewDialogState extends State duration: const Duration(milliseconds: 300), child: SizedBox( width: double.infinity, - height: toolbarType == 'input' ? keyboardHeight : emoteHeight, + height: toolbarType == 'input' + ? (_keyboardHeight > keyboardHeight + ? _keyboardHeight + : keyboardHeight) + : emoteHeight, child: EmotePanel( onChoose: (package, emote) => onChooseEmote(package, emote), ), From 2ba72e3792ef32f45ea9d1ac6568e584e7e22243 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 3 Apr 2024 23:59:24 +0800 Subject: [PATCH 16/34] 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 247f6ee0a7c077342e09c07cf7ac6eb22ab097fe Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 4 Apr 2024 23:59:53 +0800 Subject: [PATCH 17/34] 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 5500a58c32a23dfe5f684e6ecf5fd6a2b13d5b05 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 4 Apr 2024 23:41:16 +0800 Subject: [PATCH 18/34] =?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 19/34] 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 0d0e0b9adb184ae61a4c2e73b60b85e31f3efe94 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 5 Apr 2024 22:19:45 +0800 Subject: [PATCH 20/34] =?UTF-8?q?fix:=20=E5=85=B3=E6=B3=A8=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E7=8A=B6=E6=80=81=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/widgets/profile.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index 4bf7b7db..a708a35e 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -180,7 +180,9 @@ class ProfilePanel extends StatelessWidget { Obx( () => Expanded( child: TextButton( - onPressed: () => ctr.actionRelationMod(), + onPressed: () => loadingStatus + ? null + : ctr.actionRelationMod(), style: TextButton.styleFrom( foregroundColor: ctr.attribute.value == -1 ? Colors.transparent From 99645c7b4aee81a7165a8708ed5e296658a660c1 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 5 Apr 2024 23:20:44 +0800 Subject: [PATCH 21/34] fix: seekTo multiple trigger --- lib/plugin/pl_player/controller.dart | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index b385fca8..f936526b 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -101,7 +101,7 @@ class PlPlayerController { bool _isFirstTime = true; Timer? _timer; - Timer? _timerForSeek; + late Timer? _timerForSeek; Timer? _timerForVolume; Timer? _timerForShowingVolume; Timer? _timerForGettingVolume; @@ -646,9 +646,6 @@ class PlPlayerController { /// 跳转至指定位置 Future seekTo(Duration position, {type = 'seek'}) async { - // if (position >= duration.value) { - // position = duration.value - const Duration(milliseconds: 100); - // } if (position < Duration.zero) { position = Duration.zero; } @@ -661,21 +658,13 @@ class PlPlayerController { await _videoPlayerController?.stream.buffer.first; } await _videoPlayerController?.seek(position); - // if (playerStatus.stopped) { - // play(); - // } } else { - print('seek duration else'); _timerForSeek?.cancel(); - _timerForSeek = + _timerForSeek ??= Timer.periodic(const Duration(milliseconds: 200), (Timer t) async { - //_timerForSeek = null; if (duration.value.inSeconds != 0) { await _videoPlayerController!.stream.buffer.first; await _videoPlayerController?.seek(position); - // if (playerStatus.status.value == PlayerStatus.paused) { - // play(); - // } t.cancel(); _timerForSeek = null; } From a5494484aefe83fc4cb122ec1dcec9c72b9c67c8 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 6 Apr 2024 11:30:53 +0800 Subject: [PATCH 22/34] =?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 23/34] =?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 24/34] =?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 25/34] =?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 26/34] =?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 27/34] =?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 48f0b59701011e478b2d3f8efea17459ae865c0c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 8 Apr 2024 23:33:43 +0800 Subject: [PATCH 28/34] =?UTF-8?q?mod:=20=E7=9B=B8=E5=86=8C=E3=80=81?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E6=9D=83=E9=99=90=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/utils/download.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/utils/download.dart b/lib/utils/download.dart index a9c56ec0..e27335d0 100644 --- a/lib/utils/download.dart +++ b/lib/utils/download.dart @@ -11,8 +11,7 @@ class DownloadUtils { static Future requestStoragePer() async { await Permission.storage.request(); PermissionStatus status = await Permission.storage.status; - if (status == PermissionStatus.denied || - status == PermissionStatus.permanentlyDenied) { + if (status == PermissionStatus.denied) { SmartDialog.show( useSystem: true, animationType: SmartAnimationType.centerFade_otherSlide, @@ -41,8 +40,7 @@ class DownloadUtils { static Future requestPhotoPer() async { await Permission.photos.request(); PermissionStatus status = await Permission.photos.status; - if (status == PermissionStatus.denied || - status == PermissionStatus.permanentlyDenied) { + if (status == PermissionStatus.denied) { SmartDialog.show( useSystem: true, animationType: SmartAnimationType.centerFade_otherSlide, From 2bd97f800efb4e88dda04e307739cefb2dcc9b58 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 8 Apr 2024 23:43:25 +0800 Subject: [PATCH 29/34] 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 30/34] 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 31/34] =?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 32/34] =?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 33/34] =?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 34/34] =?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), ) ], );