From f7189b1ba0e95760222c2ec7a9d9d3646f7e9ae7 Mon Sep 17 00:00:00 2001 From: orz12 Date: Wed, 1 Jan 2025 17:15:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=B8=83=E5=B1=80=E4=B8=8E?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=A0=8Fbug=E3=80=81=E5=80=8D=E9=80=9F/?= =?UTF-8?q?=E5=BC=B9=E5=B9=95=E8=AE=BE=E7=BD=AE=EF=BC=8C=E5=BC=B9=E5=B9=95?= =?UTF-8?q?=E5=B8=A6=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/skeleton/video_card_v.dart | 6 + lib/common/widgets/custom_toast.dart | 2 +- lib/http/danmaku.dart | 32 +- lib/pages/about/index.dart | 117 ++-- lib/pages/danmaku/controller.dart | 2 +- lib/pages/fav_detail/view.dart | 96 +-- .../fav_detail/widget/fav_video_card.dart | 218 +++---- lib/pages/main/controller.dart | 7 +- lib/pages/media/controller.dart | 2 +- lib/pages/media/view.dart | 272 ++++---- lib/pages/setting/controller.dart | 2 +- lib/pages/setting/pages/play_speed_set.dart | 17 +- lib/pages/setting/style_setting.dart | 4 +- .../introduction/bangumi/controller.dart | 18 +- .../video/introduction/detail/controller.dart | 16 +- lib/pages/video/view.dart | 582 +++++------------- lib/pages/video/widgets/header_control.dart | 51 +- lib/plugin/pl_player/controller.dart | 47 +- .../pl_player/models/bottom_control_type.dart | 2 +- lib/plugin/pl_player/view.dart | 133 ++-- .../pl_player/widgets/bottom_control.dart | 158 ++--- .../pl_player/widgets/play_pause_btn.dart | 2 +- lib/utils/cache_manage.dart | 37 +- lib/utils/storage.dart | 24 +- 24 files changed, 817 insertions(+), 1030 deletions(-) diff --git a/lib/common/skeleton/video_card_v.dart b/lib/common/skeleton/video_card_v.dart index faaeb48b8..2d07a41c0 100644 --- a/lib/common/skeleton/video_card_v.dart +++ b/lib/common/skeleton/video_card_v.dart @@ -45,6 +45,12 @@ class VideoCardVSkeleton extends StatelessWidget { margin: const EdgeInsets.only(bottom: 12), color: Theme.of(context).colorScheme.onInverseSurface, ), + Container( + width: 150, + height: 13, + margin: const EdgeInsets.only(bottom: 12), + color: Theme.of(context).colorScheme.onInverseSurface, + ), ], ), ), diff --git a/lib/common/widgets/custom_toast.dart b/lib/common/widgets/custom_toast.dart index 40998c456..21944824d 100644 --- a/lib/common/widgets/custom_toast.dart +++ b/lib/common/widgets/custom_toast.dart @@ -12,7 +12,7 @@ class CustomToast extends StatelessWidget { @override Widget build(BuildContext context) { final double toastOpacity = - setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double; + setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0).toDouble(); return Container( margin: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30), diff --git a/lib/http/danmaku.dart b/lib/http/danmaku.dart index bb38d43ce..1fdd4c17f 100644 --- a/lib/http/danmaku.dart +++ b/lib/http/danmaku.dart @@ -4,25 +4,37 @@ import 'index.dart'; class DanmakaHttp { // 获取视频弹幕 - static Future queryDanmaku({ + static Future queryDanmaku({ required int cid, required int segmentIndex, + int maxRetries = 3, // 最大重试次数 }) async { - // 构建参数对象 Map params = { 'type': 1, 'oid': cid, 'segment_index': segmentIndex, }; - var response = await Request().get( - Api.webDanmaku, - data: params, - extra: {'resType': ResponseType.bytes}, - ); - if (response.statusCode != 200 || response.data == null) { - return DmSegMobileReply(); + + int retryCount = 0; + while (retryCount < maxRetries) { + var response = await Request().get( + Api.webDanmaku, + data: params, + extra: {'resType': ResponseType.bytes}, + ); + + if (response.statusCode == 200) { + return response.data != null + ? DmSegMobileReply.fromBuffer(response.data) + : DmSegMobileReply(); + } + print('第${retryCount + 1}次重试失败, 状态码:${response.statusCode}'); + retryCount++; + await Future.delayed(const Duration(seconds: 1)); // 重试间隔时间 } - return DmSegMobileReply.fromBuffer(response.data); + + print('所有重试失败,返回默认值'); + return DmSegMobileReply(); } static Future shootDanmaku({ diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 0f599b637..f22597573 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:PiliPalaX/http/index.dart'; import 'package:PiliPalaX/models/github/latest.dart'; @@ -50,41 +51,29 @@ class _AboutPageState extends State { ), body: ListView( children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 150), - child: ExcludeSemantics( - child: Image.asset( + Column(children: [ + Image.asset( + excludeFromSemantics: true, + height: 150, 'assets/images/logo/logo_android_2.png', - )), - ), - ListTile( - title: Text('PiliPalaX', - textAlign: TextAlign.center, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(height: 2)), - subtitle: Row(children: [ - const Spacer(), - Text( - '使用Flutter开发的B站第三方客户端', - textAlign: TextAlign.center, - style: TextStyle(color: Theme.of(context).colorScheme.outline), - semanticsLabel: '与你一起,发现不一样的世界', - ), - const Icon( - Icons.accessibility_new, - semanticLabel: "无障碍适配", - size: 18, - ), - const Spacer(), - ]), - ), + ), + Text('PiliPalaX', + textAlign: TextAlign.left, + style: Theme.of(context).textTheme.titleMedium, + semanticsLabel: 'PiliPalaX 与你一起,发现不一样的世界'), + Text( + '使用Flutter开发的B站第三方客户端⁺', + textAlign: TextAlign.left, + style: TextStyle( + color: Theme.of(context).colorScheme.outline, height: 3), + semanticsLabel: '使用Flutter开发的B站第三方客户端(带无障碍适配)', + ), + ]), Obx( () => ListTile( onTap: () => _aboutController.tapOnVersion(), title: const Text('当前版本'), - leading: const Icon(Icons.commit_outlined), + leading: Icon(MdiIcons.sourceCommitLocal), trailing: Text(_aboutController.currentVersion.value, style: subTitleStyle), ), @@ -93,7 +82,7 @@ class _AboutPageState extends State { () => ListTile( onTap: () => _aboutController.onUpdate(), title: const Text('最新版本'), - leading: const Icon(Icons.flag_outlined), + leading: Icon(MdiIcons.newBox), trailing: Text( _aboutController.isLoading.value ? '正在获取' @@ -119,26 +108,16 @@ class _AboutPageState extends State { ), ListTile( onTap: () => _aboutController.githubUrl(), - leading: const Icon(Icons.star_outline_outlined), - title: const Text('Github开源仓库'), + leading: Icon(MdiIcons.github), + title: const Text('开源仓库⭐'), trailing: Text( - 'github.com/orz12/PiliPalaX', + 'Github.com/orz12/PiliPalaX', style: subTitleStyle, ), ), - ListTile( - onTap: () => _aboutController.feedback(context), - leading: const Icon(Icons.feedback_outlined), - title: const Text('问题反馈'), - trailing: Icon( - Icons.arrow_forward_ios, - size: 16, - color: outline, - ), - ), ListTile( onTap: () => _aboutController.qqGroup(), - leading: const Icon(Icons.group_add_outlined), + leading: Icon(MdiIcons.qqchat), title: const Text('QQ群'), trailing: Text( '392176105', @@ -147,19 +126,29 @@ class _AboutPageState extends State { ), ListTile( onTap: () => _aboutController.tgChannel(), - leading: const Icon(Icons.group_add_outlined), - title: const Text('TG频道'), + leading: Icon(MdiIcons.sendCircle), + title: const Text('TG群'), trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), ), ListTile( - onTap: () => _aboutController.aPay(), - leading: const Icon(Icons.wallet_giftcard_outlined), - title: const Text('赞赏'), - trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), + onTap: () => _aboutController.feedback(context), + leading: Icon(MdiIcons.chatAlert), + title: const Text('问题反馈'), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16, + color: outline, + ), ), + // ListTile( + // onTap: () => _aboutController.aPay(), + // leading: const Icon(Icons.wallet_giftcard_outlined), + // title: const Text('赞赏'), + // trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), + // ), ListTile( onTap: () => _aboutController.logs(), - leading: const Icon(Icons.bug_report_outlined), + leading: const Icon(Icons.bug_report), title: const Text('错误日志'), trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), ), @@ -168,14 +157,14 @@ class _AboutPageState extends State { await CacheManage().clearCacheAll(context); getCacheSize(); }, - leading: const Icon(Icons.delete_outline), + leading: const Icon(Icons.delete), title: const Text('清除缓存'), - subtitle: Text('图片及网络缓存 $cacheSize', style: subTitleStyle), + trailing: Text('图片及网络缓存 $cacheSize', style: subTitleStyle), ), ListTile( title: const Text('导入/导出设置'), dense: false, - leading: const Icon(Icons.import_export_outlined), + leading: const Icon(Icons.import_export), onTap: () async { await showDialog( context: context, @@ -244,7 +233,7 @@ class _AboutPageState extends State { }), ListTile( title: const Text('重置所有设置'), - leading: const Icon(Icons.settings_backup_restore_outlined), + leading: const Icon(Icons.settings_backup_restore), onTap: () async { await showDialog( context: context, @@ -301,7 +290,7 @@ class AboutController extends GetxController { late LatestDataModel remoteAppInfo; RxBool isUpdate = true.obs; RxBool isLoading = true.obs; - late LatestDataModel data; + LatestDataModel? data; RxInt count = 0.obs; @override @@ -348,8 +337,8 @@ class AboutController extends GetxController { return false; } data = LatestDataModel.fromJson(result.data[0]); - remoteAppInfo = data; - remoteVersion.value = data.tagName!; + remoteAppInfo = data!; + remoteVersion.value = data!.tagName!; isUpdate.value = Utils.needUpdate(currentVersion.value, remoteVersion.value); isLoading.value = false; @@ -357,6 +346,12 @@ class AboutController extends GetxController { // 跳转下载/本地更新 Future onUpdate() async { + if (data == null) { + SmartDialog.showLoading(msg: '正在尝试从Github获取最新版本'); + await getRemoteApp(); + SmartDialog.dismiss(); + return; + } Utils.matchVersion(data); } @@ -397,7 +392,7 @@ class AboutController extends GetxController { context: context, builder: (context) { return SimpleDialog( - title: const Text('问题反馈(建议直接加群反馈)'), + title: const Text('问题反馈\n(建议直接加群)'), children: [ ListTile( title: const Text('GitHub Issue'), @@ -438,7 +433,7 @@ class AboutController extends GetxController { } } - // tg频道 + // tg群 tgChannel() { Clipboard.setData( const ClipboardData(text: 'https://t.me/+162zlPtZlT9hNWVl'), diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart index 6194c9342..c6256c4d3 100644 --- a/lib/pages/danmaku/controller.dart +++ b/lib/pages/danmaku/controller.dart @@ -8,7 +8,7 @@ class PlDanmakuController { final int cid; static int danmakuWeight = 0; static List> danmakuFilter = []; - PlDanmakuController(this.cid){ + PlDanmakuController(this.cid) { refresh(); } diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 8e488c95b..25780d5c0 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -200,60 +200,60 @@ class _FavDetailPageState extends State { // ), // ), // ), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { + SliverPadding( + padding: + const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done || + !snapshot.hasData) { + // 骨架屏 + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.4, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), + ); + } Map data = snapshot.data; - if (data['status']) { - if (_favDetailController.item!.mediaCount == 0) { - return const NoData(); - } else { - List favList = _favDetailController.favList; - return Obx( - () => favList.isEmpty - ? const SliverToBoxAdapter(child: SizedBox()) - : SliverGrid( - gridDelegate: - SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - childAspectRatio: - StyleString.aspectRatio * 2.4, - mainAxisExtent: 0), - delegate: - SliverChildBuilderDelegate((context, index) { - return FavVideoCardH( - videoItem: favList[index], - callFn: () => _favDetailController - .onCancelFav(favList[index].id), - ); - }, childCount: favList.length), - ), - ); - } - } else { + if (!data['status']) { return HttpError( errMsg: data['msg'], fn: () => setState(() {}), ); } - } else { - // 骨架屏 - return SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.4, - mainAxisExtent: 0), - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoCardHSkeleton(); - }, childCount: 10), - ); - } - }, + if (_favDetailController.item!.mediaCount == 0) { + return const NoData(); + } + List favList = _favDetailController.favList; + return Obx(() { + if (favList.isEmpty) { + return const SliverToBoxAdapter(child: SizedBox()); + } + return SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.4, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate((context, index) { + return FavVideoCardH( + videoItem: favList[index], + callFn: () => + _favDetailController.onCancelFav(favList[index].id), + ); + }, childCount: favList.length), + ); + }); + }, + ), ), SliverToBoxAdapter( child: Container( diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 1cfc1c84d..96ca75ecb 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -64,69 +64,61 @@ class FavVideoCardH extends StatelessWidget { epId != null ? SearchType.media_bangumi : SearchType.video, }); }, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder( - builder: (context, boxConstraints) { - double width = - (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; - return SizedBox( - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: videoItem.pic, - width: maxWidth, - height: maxHeight, - ), - ), - PBadge( - text: Utils.timeFormat(videoItem.duration!), - right: 6.0, - bottom: 6.0, - type: 'gray', - ), - if (videoItem.ogv != null) ...[ - PBadge( - text: videoItem.ogv['type_name'], - top: 6.0, - right: 6.0, - bottom: null, - left: null, - ), - ], - ], - ); - }, - ), - ), - VideoContent( - videoItem: videoItem, - callFn: callFn, - searchType: searchType, - ) - ], + child: LayoutBuilder( + builder: (context, boxConstraints) { + double width = + (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; + return SizedBox( + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: videoItem.pic, + width: maxWidth, + height: maxHeight, + ), + ), + PBadge( + text: Utils.timeFormat(videoItem.duration!), + right: 6.0, + bottom: 6.0, + type: 'gray', + ), + if (videoItem.ogv != null) ...[ + PBadge( + text: videoItem.ogv['type_name'], + top: 6.0, + right: 6.0, + bottom: null, + left: null, + ), + ], + ], + ); + }, ), - ); - }, + ), + VideoContent( + videoItem: videoItem, + callFn: callFn, + searchType: searchType, + ) + ], ), - ), - ], + ); + }, ), ); } @@ -175,21 +167,12 @@ class VideoContent extends StatelessWidget { ], const Spacer(), Text( - Utils.dateFormat(videoItem.favTime), + "${Utils.dateFormat(videoItem.pubdate!, formatType: 'day')} ${videoItem.owner.name}", style: TextStyle( - fontSize: 11, - color: Theme.of(context).colorScheme.outline), - ), - if (videoItem.owner.name != '') ...[ - Text( - videoItem.owner.name, - style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, - color: Theme.of(context).colorScheme.outline, - ), - ), - ], + color: Theme.of(context).colorScheme.outline), + ), Padding( padding: const EdgeInsets.only(top: 2), child: Row( @@ -207,52 +190,53 @@ class VideoContent extends StatelessWidget { ), ], ), - searchType != 1 - ? Positioned( - right: 0, - bottom: -4, - child: IconButton( - tooltip: '取消收藏', - style: ButtonStyle( - padding: WidgetStateProperty.all(EdgeInsets.zero), - ), - onPressed: () { - showDialog( - context: Get.context!, - builder: (context) { - return 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 { - await callFn!(); - Get.back(); - }, - child: const Text('确定取消'), - ) - ], - ); - }, + if (searchType != 1) + Positioned( + right: 0, + bottom: -6, + width: 28, + height: 28, + child: IconButton( + tooltip: '取消收藏', + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + ), + onPressed: () { + showDialog( + context: Get.context!, + builder: (context) { + return 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 { + await callFn!(); + Get.back(); + }, + child: const Text('确定取消'), + ) + ], ); }, - icon: Icon( - Icons.clear_outlined, - color: Theme.of(context).colorScheme.outline, - size: 18, - ), - ), - ) - : const SizedBox(), + ); + }, + icon: Icon( + Icons.clear_outlined, + color: Theme.of(context).colorScheme.outline, + size: 18, + ), + ), + ), ], ), ), diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index cc0608d2d..0ba79f226 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -16,10 +16,9 @@ import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { List pages = [ - const HomePage(), - // const RankPage(), - const DynamicsPage(), - const MediaPage(), + HomePage(key: GlobalKey()), + DynamicsPage(key: GlobalKey()), + MediaPage(key: GlobalKey()), ]; RxList navigationBars = defaultNavigationBars.obs; final StreamController bottomBarStream = diff --git a/lib/pages/media/controller.dart b/lib/pages/media/controller.dart index 61c931332..df5f01382 100644 --- a/lib/pages/media/controller.dart +++ b/lib/pages/media/controller.dart @@ -59,7 +59,7 @@ class MediaController extends GetxController { } var res = await await UserHttp.userfavFolder( pn: 1, - ps: 5, + ps: 10, mid: mid ?? GStorage.userInfo.get('userInfoCache').mid, ); favFolderData.value = res['data']; diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 67946b382..a70c6c990 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:PiliPalaX/pages/setting/style_setting.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -10,6 +11,8 @@ import 'package:PiliPalaX/pages/main/index.dart'; import 'package:PiliPalaX/pages/media/index.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../common/constants.dart'; + class MediaPage extends StatefulWidget { const MediaPage({super.key}); @@ -176,33 +179,18 @@ class _MediaPageState extends State title: Padding( padding: const EdgeInsets.only(left: 10), child: Obx( - () => Text.rich( - TextSpan( - children: [ - TextSpan( - text: '我的收藏 ', - style: TextStyle( - fontSize: - Theme.of(context).textTheme.titleMedium!.fontSize, - fontWeight: FontWeight.bold), - ), - if (mediaController.favFolderData.value.count != null) - TextSpan( - text: "${mediaController.favFolderData.value.count} ", - style: TextStyle( - fontSize: - Theme.of(context).textTheme.titleSmall!.fontSize, - color: Theme.of(context).colorScheme.primary, - ), - ), - WidgetSpan( - child: Icon( - Icons.arrow_forward_ios, - size: 18, - color: Theme.of(context).colorScheme.primary, - )), - ], - ), + () => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '我的收藏 (${mediaController.favFolderData.value.count ?? 0}) ', + style: TextStyle( + fontSize: + Theme.of(context).textTheme.titleMedium!.fontSize, + fontWeight: FontWeight.bold), + ), + const Icon(Icons.arrow_forward_ios, size: 16), + ], ), ), ), @@ -219,78 +207,75 @@ class _MediaPageState extends State ), ), ), - // const SizedBox(height: 10), SizedBox( width: double.infinity, - height: MediaQuery.textScalerOf(context).scale(200), + height: 200, child: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - Map data = snapshot.data as Map; - if (data['status']) { - List favFolderList = - mediaController.favFolderData.value.list!; - int favFolderCount = - mediaController.favFolderData.value.count!; - bool flag = favFolderCount > favFolderList.length; - return Obx(() => ListView.builder( - itemCount: - mediaController.favFolderData.value.list!.length + - (flag ? 1 : 0), - itemBuilder: (context, index) { - if (flag && index == favFolderList.length) { - return Padding( - padding: const EdgeInsets.only( - right: 14, bottom: 35), - child: Center( - child: IconButton( - tooltip: '查看更多', - style: ButtonStyle( - padding: WidgetStateProperty.all( - EdgeInsets.zero), - backgroundColor: - WidgetStateProperty.resolveWith( - (states) { - return Theme.of(context) - .colorScheme - .primaryContainer - .withOpacity(0.5); - }), - ), - onPressed: () => Get.toNamed('/fav'), - icon: Icon( - Icons.arrow_forward_ios, - size: 18, - color: Theme.of(context) - .colorScheme - .primary, - ), - ), - )); - } else { - return FavFolderItem( - item: mediaController - .favFolderData.value.list![index], - index: index); - } - }, - scrollDirection: Axis.horizontal, - )); - } else { - return SizedBox( - height: 160, - child: Center(child: Text(data['msg'])), - ); - } - } else { - // 骨架屏 - return const SizedBox(); - } - }), + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done || + !snapshot.hasData) { + return const SizedBox(); + } + Map data = snapshot.data as Map; + if (!data['status']) { + return SizedBox( + height: 160, + child: Center(child: Text(data['msg'])), + ); + } + return Obx( + () { + List favFolderList = + mediaController.favFolderData.value.list!; + int favFolderCount = + mediaController.favFolderData.value.count!; + int extra = favFolderCount > favFolderList.length ? 1 : 0; + return ListView.builder( + padding: const EdgeInsets.only(left: StyleString.safeSpace), + itemCount: favFolderList.length + extra, + itemBuilder: (context, index) { + if (index < favFolderList.length) { + return Padding( + padding: const EdgeInsets.only( + left: StyleString.cardSpace), + child: FavFolderItem( + item: mediaController + .favFolderData.value.list![index], + index: index)); + } + return Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 40), + child: IconButton( + tooltip: '查看更多', + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + backgroundColor: + WidgetStateProperty.resolveWith((states) { + return Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.5); + }), + ), + onPressed: () => Get.toNamed('/fav'), + icon: Icon( + Icons.arrow_forward_ios, + size: 18, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ); + }, + scrollDirection: Axis.horizontal, + ); + }, + ); + }, + ), ), ], ); @@ -305,67 +290,44 @@ class FavFolderItem extends StatelessWidget { Widget build(BuildContext context) { String heroTag = Utils.makeHeroTag(item!.fid); - return Container( - margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14), - child: GestureDetector( - onTap: () => Get.toNamed('/favDetail', - arguments: item, - parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 12), - Container( - width: 180, - height: 110, - margin: const EdgeInsets.only(bottom: 8), - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Theme.of(context) - .colorScheme - .onInverseSurface - .withOpacity(0.4), - boxShadow: [ - BoxShadow( - color: Theme.of(context) - .colorScheme - .onInverseSurface - .withOpacity(0.4), - offset: const Offset(4, -12), // 阴影与容器的距离 - blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。 - spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。 + return GestureDetector( + onTap: () => Get.toNamed('/favDetail', + arguments: item, + parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 160, + height: 130, + margin: const EdgeInsets.only(bottom: 8), + child: LayoutBuilder( + builder: (context, BoxConstraints box) { + return Hero( + tag: heroTag, + child: NetworkImgLayer( + src: item!.cover, + width: box.maxWidth, + height: box.maxHeight, ), - ], - ), - child: LayoutBuilder( - builder: (context, BoxConstraints box) { - return Hero( - tag: heroTag, - child: NetworkImgLayer( - src: item!.cover, - width: box.maxWidth, - height: box.maxHeight, - ), - ); - }, - ), - ), - Text( - ' ${item!.title}', - overflow: TextOverflow.fade, - maxLines: 1, + ); + }, ), - Text( - ' 共${item!.mediaCount}条视频', - style: Theme.of(context) - .textTheme - .labelSmall! - .copyWith(color: Theme.of(context).colorScheme.outline), - ) - ], - ), + ), + Text( + ' ${item!.title}', + overflow: TextOverflow.fade, + maxLines: 1, + ), + Text( + ' 共${item!.mediaCount}条视频', + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline), + ) + ], ), ); } diff --git a/lib/pages/setting/controller.dart b/lib/pages/setting/controller.dart index 4c1015ea6..235611104 100644 --- a/lib/pages/setting/controller.dart +++ b/lib/pages/setting/controller.dart @@ -38,7 +38,7 @@ class SettingController extends GetxController { feedBackEnable.value = setting.get(SettingBoxKey.feedBackEnable, defaultValue: false); toastOpacity.value = - setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0); + setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0).toDouble(); picQuality.value = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); themeType.value = ThemeType.values[setting.get(SettingBoxKey.themeMode, diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index afbedde22..5a0025e68 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -55,14 +55,17 @@ class _PlaySpeedPageState extends State { void initState() { super.initState(); // 默认倍速 - playSpeedDefault = - videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); + playSpeedDefault = videoStorage + .get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0) + .toDouble(); // 默认长按倍速 - longPressSpeedDefault = - videoStorage.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0); - // 自定义倍速 - customSpeedsList = videoStorage.get(VideoBoxKey.customSpeedsList, - defaultValue: [0.5, 0.75, 1.25, 1.5, 1.75, 3.0]); + longPressSpeedDefault = videoStorage + .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0) + .toDouble(); + List defaultList = [0.5, 0.75, 1.25, 1.5, 1.75, 3.0]; + customSpeedsList = List.from(videoStorage + .get(VideoBoxKey.customSpeedsList, defaultValue: defaultList) + .map((e) => e.toDouble())); enableAutoLongPressSpeed = settingStorage .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); if (enableAutoLongPressSpeed) { diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 29a97f9e9..b782c15e6 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -16,6 +16,7 @@ import 'package:PiliPalaX/utils/storage.dart'; import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/side_bar_position.dart'; import '../../models/common/up_panel_position.dart'; +import '../../plugin/pl_player/controller.dart'; import '../../plugin/pl_player/utils/fullscreen.dart'; import '../../models/common/nav_bar_config.dart'; import 'controller.dart'; @@ -87,6 +88,7 @@ class _StyleSettingState extends State { AutoOrientation.portraitUpMode(); SmartDialog.showToast('已关闭横屏适配'); } + PlPlayerController.updateSettings(); }), // const SetSwitchItem( // title: '改用侧边栏', @@ -101,7 +103,7 @@ class _StyleSettingState extends State { title: Text('主页侧栏布局', style: titleStyle), leading: const Icon(Icons.chrome_reader_mode_outlined), subtitle: Text( - '当前主页侧边栏位置:${sideBarPosition.labels},开启后底栏与顶栏将被替换为侧栏,横屏或折叠屏推荐使用', + '当前:${sideBarPosition.labels}。开启后底栏与顶栏将被替换为侧栏,横屏或折叠屏推荐使用', style: subTitleStyle), onTap: () async { SideBarPosition? result = await showDialog( diff --git a/lib/pages/video/introduction/bangumi/controller.dart b/lib/pages/video/introduction/bangumi/controller.dart index 4871aca10..c554050c6 100644 --- a/lib/pages/video/introduction/bangumi/controller.dart +++ b/lib/pages/video/introduction/bangumi/controller.dart @@ -265,22 +265,28 @@ class BangumiIntroController extends GetxController { return AlertDialog( title: const Text('请选择'), actions: [ - TextButton( + TextButton.icon( onPressed: () { Clipboard.setData(ClipboardData(text: videoUrl)); SmartDialog.showToast('已复制'); + Get.back(); }, - child: const Text('复制链接到剪贴板')), - TextButton( + icon: const Icon(Icons.copy), + label: const Text('复制链接')), + TextButton.icon( onPressed: () { launchUrl(Uri.parse(videoUrl)); + Get.back(); }, - child: const Text('其它app打开')), - TextButton( + icon: const Icon(Icons.open_in_browser), + label: const Text('其它app打开')), + TextButton.icon( onPressed: () async { await Share.share(videoUrl).whenComplete(() {}); + Get.back(); }, - child: const Text('分享视频')), + icon: const Icon(Icons.share), + label: const Text('分享视频')), ], ); }); diff --git a/lib/pages/video/introduction/detail/controller.dart b/lib/pages/video/introduction/detail/controller.dart index c66832f8d..9f8d5d088 100644 --- a/lib/pages/video/introduction/detail/controller.dart +++ b/lib/pages/video/introduction/detail/controller.dart @@ -393,19 +393,22 @@ class VideoIntroController extends GetxController { return AlertDialog( title: const Text('请选择'), actions: [ - TextButton( + TextButton.icon( onPressed: () { Clipboard.setData(ClipboardData(text: videoUrl)); SmartDialog.showToast('已复制'); Get.back(); }, - child: const Text('复制链接')), - TextButton( + icon: const Icon(Icons.copy), + label: const Text('复制链接')), + TextButton.icon( onPressed: () { launchUrl(Uri.parse(videoUrl)); + Get.back(); }, - child: const Text('其它app打开')), - TextButton( + icon: const Icon(Icons.open_in_browser), + label: const Text('其它app打开')), + TextButton.icon( onPressed: () async { await Share.share('${videoDetail.value.title} ' 'UP主: ${videoDetail.value.owner!.name!}' @@ -413,7 +416,8 @@ class VideoIntroController extends GetxController { .whenComplete(() {}); Get.back(); }, - child: const Text('分享视频')), + icon: const Icon(Icons.share), + label: const Text('分享视频')), ], ); }); diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index be0866d67..b0e006a4c 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -71,6 +71,10 @@ class _VideoDetailPageState extends State late StreamSubscription fullScreenStatusListener; // late final MethodChannel onUserLeaveHintListener; // StreamSubscription? _bufferedListener; + late String myRouteName; + bool isShowing = true; + final GlobalKey relatedVideoPanelKey = GlobalKey(); + final GlobalKey videoPlayerFutureKey = GlobalKey(); @override void initState() { @@ -78,6 +82,7 @@ class _VideoDetailPageState extends State if (Get.arguments != null && Get.arguments['heroTag'] != null) { heroTag = Get.arguments['heroTag']; } + myRouteName = Get.rawRoute!.settings.name!; videoDetailController = Get.put(VideoDetailController(), tag: heroTag); if (!videoDetailController.autoPlay.value && floatingManager.containsFloating(globalId)) { @@ -473,40 +478,40 @@ class _VideoDetailPageState extends State // } Widget get plPlayer => FutureBuilder( + key: videoPlayerFutureKey, future: _futureBuilderFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData && snapshot.data['status']) { - return Obx( - () => (!videoDetailController.autoPlay.value && - videoDetailController.isShowCover.value) || - plPlayerController == null || - plPlayerController!.videoController == null - ? nil - : PLVideoPlayer( - key: Key(heroTag), - controller: plPlayerController!, - videoIntroController: - videoDetailController.videoType == SearchType.video - ? videoIntroController - : null, - bangumiIntroController: videoDetailController.videoType == - SearchType.media_bangumi - ? bangumiIntroController - : null, - headerControl: videoDetailController.headerControl, - danmuWidget: Obx( - () => PlDanmaku( - key: Key( - videoDetailController.danmakuCid.value.toString()), - cid: videoDetailController.danmakuCid.value, - playerController: plPlayerController!, - ), - ), - ), - ); - } else { + if (!snapshot.hasData || !snapshot.data['status']) { return const SizedBox(); } + return Obx(() { + if ((!videoDetailController.autoPlay.value && + videoDetailController.isShowCover.value) || + plPlayerController == null || + plPlayerController!.videoController == null) { + return nil; + } + return PLVideoPlayer( + key: Key(heroTag), + controller: plPlayerController!, + videoIntroController: + videoDetailController.videoType == SearchType.video + ? videoIntroController + : null, + bangumiIntroController: + videoDetailController.videoType == SearchType.media_bangumi + ? bangumiIntroController + : null, + headerControl: videoDetailController.headerControl, + danmuWidget: Obx( + () => PlDanmaku( + key: Key(videoDetailController.danmakuCid.value.toString()), + cid: videoDetailController.danmakuCid.value, + playerController: plPlayerController!, + ), + ), + ); + }); }); Widget get manualPlayerWidget => Obx(() => Visibility( @@ -551,6 +556,56 @@ class _VideoDetailPageState extends State ), ]))); + Widget playerStack(videoWidth, videoHeight) => Stack( + children: [ + // if (isShowing) plPlayer, + plPlayer, + + /// 关闭自动播放时 手动播放 + if (!videoDetailController.autoPlay.value) ...[ + Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: GestureDetector( + onTap: handlePlay, + child: NetworkImgLayer( + type: 'emote', + src: videoDetailController.videoItem['pic'], + width: videoWidth, + height: videoHeight, + ), + ), + ), + ), + ), + manualPlayerWidget, + ] + ], + ); + Widget playerPopScope(videoWidth, videoHeight) => PopScope( + canPop: isFullScreen.value != true, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (isFullScreen.value == true) { + plPlayerController!.triggerFullScreen(status: false); + } + if (MediaQuery.of(context).orientation == Orientation.landscape && + !horizontalScreen) { + verticalScreenForTwoSeconds(); + } + if (didPop) { + triggerFloatingWindowWhenLeaving(); + } + }, + child: playerStack(videoWidth, videoHeight), + ); + + Widget get relatedVideo => + RelatedVideoPanel(key: relatedVideoPanelKey, heroTag: heroTag); + Widget get childWhenDisabled => SafeArea( top: !removeSafeArea && MediaQuery.of(context).orientation == Orientation.portrait && @@ -582,13 +637,6 @@ class _VideoDetailPageState extends State ), body: Column( children: [ - // const SizedBox( - // height: 300, - // child: - // TextButton(onPressed: (){ - // - // }) - // ), Obx( () { double videoHeight = context.width * 9 / 16; @@ -602,14 +650,14 @@ class _VideoDetailPageState extends State Orientation.landscape && !horizontalScreen && !isFullScreen.value && - // isShowing && + isShowing && mounted) { hideStatusBar(); } if (MediaQuery.of(context).orientation == Orientation.portrait && !isFullScreen.value && - // isShowing && + isShowing && mounted) { if (!removeSafeArea) showStatusBar(); } @@ -626,65 +674,14 @@ class _VideoDetailPageState extends State : MediaQuery.of(context).padding.top) : videoHeight, width: context.width, - child: PopScope( - canPop: isFullScreen.value != true && - (horizontalScreen || - MediaQuery.of(context).orientation == - Orientation.portrait), - onPopInvokedWithResult: (bool didPop, Object? result) { - if (isFullScreen.value == true) { - plPlayerController! - .triggerFullScreen(status: false); - } - if (MediaQuery.of(context).orientation == - Orientation.landscape && - !horizontalScreen) { - verticalScreenForTwoSeconds(); - } - if (didPop) { - triggerFloatingWindowWhenLeaving(); - } - }, - child: Stack( - children: [ - // if (isShowing) plPlayer, - plPlayer, - - /// 关闭自动播放时 手动播放 - if (!videoDetailController - .autoPlay.value) ...[ - Obx( - () => Visibility( - visible: - videoDetailController.isShowCover.value, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: GestureDetector( - onTap: handlePlay, - child: NetworkImgLayer( - type: 'emote', - src: videoDetailController - .videoItem['pic'], - width: videoWidth, - height: videoHeight, - ), - ), - ), - ), - ), - manualPlayerWidget, - ] - ], - )), + child: playerPopScope(videoWidth, videoHeight), ); }, ), Expanded( child: ColoredBox( key: Key(heroTag), - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: Column( children: [ // Opacity( @@ -697,7 +694,7 @@ class _VideoDetailPageState extends State // controller: videoDetailController.tabCtr, // dividerColor: Colors.transparent, // indicatorColor: - // Theme.of(context).colorScheme.background, + // Theme.of(context).colorScheme.surface, // tabs: videoDetailController.tabs // .map((String name) => Tab(text: name)) // .toList(), @@ -731,7 +728,7 @@ class _VideoDetailPageState extends State .withOpacity(0.06), ), ), - RelatedVideoPanel(heroTag: heroTag), + relatedVideo, ], ), Obx( @@ -766,50 +763,7 @@ class _VideoDetailPageState extends State SizedBox( height: videoHeight, width: isFullScreen.value == true ? context.width : videoWidth, - child: PopScope( - canPop: isFullScreen.value != true, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (isFullScreen.value == true) { - plPlayerController!.triggerFullScreen(status: false); - } - if (MediaQuery.of(context).orientation == - Orientation.landscape && - !horizontalScreen) { - verticalScreenForTwoSeconds(); - } - if (didPop) { - triggerFloatingWindowWhenLeaving(); - } - }, - child: Stack(children: [ - // if (isShowing) plPlayer, - plPlayer, - - /// 关闭自动播放时 手动播放 - if (!videoDetailController.autoPlay.value) ...[ - Obx( - () => Visibility( - visible: videoDetailController.isShowCover.value, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: GestureDetector( - onTap: handlePlay, - child: NetworkImgLayer( - type: 'emote', - src: videoDetailController.videoItem['pic'], - width: videoWidth, - height: videoHeight, - ), - ), - ), - ), - ), - manualPlayerWidget, - ] - ]), - ), + child: playerPopScope(videoWidth, videoHeight), ), Expanded( child: TabBarView( @@ -836,7 +790,7 @@ class _VideoDetailPageState extends State Theme.of(context).dividerColor.withOpacity(0.06), ), ), - RelatedVideoPanel(heroTag: heroTag), + relatedVideo, ], ), Obx( @@ -864,50 +818,7 @@ class _VideoDetailPageState extends State : (MediaQuery.of(context).padding.top + MediaQuery.of(context).padding.bottom)) : videoHeight, - child: PopScope( - canPop: isFullScreen.value != true, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (isFullScreen.value == true) { - plPlayerController!.triggerFullScreen(status: false); - } - if (MediaQuery.of(context).orientation == - Orientation.landscape && - !horizontalScreen) { - verticalScreenForTwoSeconds(); - } - if (didPop) { - triggerFloatingWindowWhenLeaving(); - } - }, - child: Stack(children: [ - // if (isShowing) plPlayer, - plPlayer, - - /// 关闭自动播放时 手动播放 - if (!videoDetailController.autoPlay.value) ...[ - Obx( - () => Visibility( - visible: videoDetailController.isShowCover.value, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: GestureDetector( - onTap: handlePlay, - child: NetworkImgLayer( - type: 'emote', - src: videoDetailController.videoItem['pic'], - width: videoWidth, - height: videoHeight, - ), - ), - ), - ), - ), - manualPlayerWidget, - ] - ]), - ), + child: playerPopScope(videoWidth, videoHeight), ), Expanded( child: Row(children: [ @@ -917,7 +828,7 @@ class _VideoDetailPageState extends State slivers: [ if (videoDetailController.videoType == SearchType.video) ...[ VideoIntroPanel(heroTag: heroTag), - RelatedVideoPanel(heroTag: heroTag), + relatedVideo, ] else if (videoDetailController.videoType == SearchType.media_bangumi) ...[ Obx(() => BangumiIntroPanel( @@ -951,7 +862,7 @@ class _VideoDetailPageState extends State slivers: [ if (videoDetailController.videoType == SearchType.video) ...[ VideoIntroPanel(heroTag: heroTag), - RelatedVideoPanel(heroTag: heroTag), + relatedVideo, ] else if (videoDetailController.videoType == SearchType.media_bangumi) ...[ Obx(() => BangumiIntroPanel( @@ -962,52 +873,7 @@ class _VideoDetailPageState extends State SizedBox( height: videoHeight, width: isFullScreen.value == true ? context.width : videoWidth, - child: PopScope( - canPop: isFullScreen.value != true, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (isFullScreen.value == true) { - plPlayerController!.triggerFullScreen(status: false); - } - if (MediaQuery.of(context).orientation == - Orientation.landscape && - !horizontalScreen) { - verticalScreenForTwoSeconds(); - } - if (didPop) { - triggerFloatingWindowWhenLeaving(); - } - }, - child: Stack( - children: [ - // if (isShowing) plPlayer, - plPlayer, - - /// 关闭自动播放时 手动播放 - if (!videoDetailController.autoPlay.value) ...[ - Obx( - () => Visibility( - visible: videoDetailController.isShowCover.value, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: GestureDetector( - onTap: handlePlay, - child: NetworkImgLayer( - type: 'emote', - src: videoDetailController.videoItem['pic'], - width: videoWidth, - height: videoHeight, - ), - ), - ), - ), - ), - manualPlayerWidget, - ] - ], - ), - ), + child: playerPopScope(videoWidth, videoHeight), ), Expanded( child: Obx( @@ -1019,159 +885,70 @@ class _VideoDetailPageState extends State ), ), ), - // Expanded( - // child: TabBarView( - // physics: const CustomTabBarViewScrollPhysics(), - // controller: videoDetailController.tabCtr, - // children: [ - // CustomScrollView( - // key: const PageStorageKey('简介'), - // slivers: [ - // if (videoDetailController.videoType == - // SearchType.video) ...[ - // const VideoIntroPanel(), - // ] else if (videoDetailController.videoType == - // SearchType.media_bangumi) ...[ - // Obx(() => BangumiIntroPanel( - // cid: videoDetailController.cid.value)), - // ], - // SliverToBoxAdapter( - // child: Divider( - // indent: 12, - // endIndent: 12, - // color: Theme.of(context).dividerColor.withOpacity(0.06), - // ), - // ), - // const RelatedVideoPanel(), - // ], - // ), - // Obx( - // () => VideoReplyPanel( - // key: const PageStorageKey('评论'), - // bvid: videoDetailController.bvid, - // oid: videoDetailController.oid.value, - // ), - // ) - // ], - // ), - // ), ]); } final double videoWidth = max(context.height / context.width * 1.04, 1 / 2) * context.width; final double videoHeight = videoWidth * 9 / 16; return Row(children: [ - Column( - children: [ - SizedBox( - width: isFullScreen.value == true ? context.width : videoWidth, - height: - isFullScreen.value == true ? context.height : videoHeight, - child: PopScope( - canPop: isFullScreen.value != true, - onPopInvokedWithResult: (bool didPop, Object? result) { - if (isFullScreen.value == true) { - plPlayerController!.triggerFullScreen(status: false); - } - if (MediaQuery.of(context).orientation == - Orientation.landscape && - !horizontalScreen) { - verticalScreenForTwoSeconds(); - } - if (didPop) { - triggerFloatingWindowWhenLeaving(); - } - }, - child: Stack( - children: [ - // if (isShowing) plPlayer, - plPlayer, - - /// 关闭自动播放时 手动播放 - if (!videoDetailController.autoPlay.value) ...[ - Obx( - () => Visibility( - visible: videoDetailController.isShowCover.value, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: GestureDetector( - onTap: handlePlay, - child: NetworkImgLayer( - type: 'emote', - src: videoDetailController.videoItem['pic'], - width: videoWidth, - height: videoHeight, - ), - ), - ), - ), - ), - manualPlayerWidget, - ] - ], - ), + SizedBox( + width: isFullScreen.value == true ? context.width : videoWidth, + height: context.height, + child: Column( + children: [ + SizedBox( + width: + isFullScreen.value == true ? context.width : videoWidth, + height: + isFullScreen.value == true ? context.height : videoHeight, + child: playerPopScope(videoWidth, videoHeight), ), - ), - Offstage( - offstage: isFullScreen.value == true, - child: SizedBox( - width: videoWidth, - height: context.height - - videoHeight - - (removeSafeArea ? 0 : MediaQuery.of(context).padding.top), - child: CustomScrollView( - key: PageStorageKey( - '简介${videoDetailController.bvid}'), - slivers: [ - if (videoDetailController.videoType == - SearchType.video) ...[ - VideoIntroPanel(heroTag: heroTag), - // RelatedVideoPanel(heroTag: heroTag), - ] else if (videoDetailController.videoType == - SearchType.media_bangumi) ...[ - Obx(() => BangumiIntroPanel( - heroTag: heroTag, - cid: videoDetailController.cid.value)), - ] - ], + Expanded( + child: SizedBox( + width: videoWidth, + height: context.height - + videoHeight - + (removeSafeArea + ? 0 + : MediaQuery.of(context).padding.top), + child: CustomScrollView( + key: PageStorageKey( + '简介${videoDetailController.bvid}'), + slivers: [ + if (videoDetailController.videoType == + SearchType.video) ...[ + VideoIntroPanel(heroTag: heroTag), + ] else if (videoDetailController.videoType == + SearchType.media_bangumi) ...[ + Obx(() => BangumiIntroPanel( + heroTag: heroTag, + cid: videoDetailController.cid.value)), + ] + ], + ), ), ), - ), - ], + ], + ), ), - Offstage( - offstage: isFullScreen.value == true, - child: SizedBox( - width: (context.width - - videoWidth - - (removeSafeArea - ? 0 - : (MediaQuery.of(context).padding.left + - MediaQuery.of(context).padding.right))), - height: context.height - - (removeSafeArea ? 0 : MediaQuery.of(context).padding.top), - child: TabBarView( - physics: const CustomTabBarViewScrollPhysics(), - controller: videoDetailController.tabCtr, - children: [ - if (videoDetailController.videoType == SearchType.video) - CustomScrollView( - slivers: [ - RelatedVideoPanel(heroTag: heroTag), - ], - ), - Obx( - () => VideoReplyPanel( - key: const PageStorageKey('评论'), - bvid: videoDetailController.bvid, - oid: videoDetailController.oid.value, - heroTag: heroTag, - ), - ) - ], - ), + Expanded( + child: TabBarView( + physics: const CustomTabBarViewScrollPhysics(), + controller: videoDetailController.tabCtr, + children: [ + if (videoDetailController.videoType == SearchType.video) + CustomScrollView( + slivers: [relatedVideo], + ), + Obx( + () => VideoReplyPanel( + key: const PageStorageKey('评论'), + bvid: videoDetailController.bvid, + oid: videoDetailController.oid.value, + heroTag: heroTag, + ), + ) + ], ), ) ]); @@ -1196,7 +973,7 @@ class _VideoDetailPageState extends State systemNavigationBarColor: Colors.transparent), ), body: Container( - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: SafeArea( left: !removeSafeArea && isFullScreen.value != true, right: !removeSafeArea && isFullScreen.value != true, @@ -1225,7 +1002,7 @@ class _VideoDetailPageState extends State systemNavigationBarColor: Colors.transparent), ), body: Container( - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: SafeArea( left: !removeSafeArea && isFullScreen.value != true, right: !removeSafeArea && isFullScreen.value != true, @@ -1270,7 +1047,6 @@ class _VideoDetailPageState extends State return childWhenDisabled; } return PiPBuilder(builder: (PiPStatusInfo? statusInfo) { - print("PiPStatusInfo${statusInfo?.status}"); switch (statusInfo?.status) { case PiPStatus.enabled: return childWhenEnabled; @@ -1282,15 +1058,6 @@ class _VideoDetailPageState extends State return childWhenDisabled; } }); - // return childWhenDisabled; - // if (Platform.isAndroid) { - // return PiPSwitcher( - // childWhenDisabled: childWhenDisabled, - // childWhenEnabled: childWhenEnabled, - // floating: floating, - // ); - // } - // return childWhenDisabled; } @override @@ -1301,9 +1068,10 @@ class _VideoDetailPageState extends State return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - // if (!isShowing) { - // return ColoredBox(color: Theme.of(context).colorScheme.background); - // } + isShowing = Get.currentRoute == myRouteName; + if (!isShowing && Get.previousRoute != myRouteName) { + return ColoredBox(color: Theme.of(context).colorScheme.surface); + } if (constraints.maxWidth > constraints.maxHeight * 1.25) { // hideStatusBar(); // videoDetailController.hiddenReplyReplyPanel(); @@ -1311,43 +1079,19 @@ class _VideoDetailPageState extends State } else if (constraints.maxWidth * (9 / 16) < (2 / 5) * constraints.maxHeight) { if (!isFullScreen.value) { - if (!removeSafeArea) showStatusBar(); + if (!removeSafeArea && isShowing) { + showStatusBar(); + } } return autoChoose(childWhenDisabled); } else { if (!isFullScreen.value) { - if (!removeSafeArea) showStatusBar(); + if (!removeSafeArea && isShowing) { + showStatusBar(); + } } return autoChoose(childWhenDisabledAlmostSquare); } - // - // final Orientation orientation = - // constraints.maxWidth > constraints.maxHeight * 1.25 - // ? Orientation.landscape - // : Orientation.portrait; - // if (orientation == Orientation.landscape) { - // if (!horizontalScreen) { - // hideStatusBar(); - // videoDetailController.hiddenReplyReplyPanel(); - // } - // } else { - // if (!isFullScreen.value) { - // showStatusBar(); - // } - // } - // if (Platform.isAndroid) { - // return PiPSwitcher( - // childWhenDisabled: - // !horizontalScreen || orientation == Orientation.portrait - // ? childWhenDisabled - // : childWhenDisabledLandscape, - // childWhenEnabled: childWhenEnabled, - // floating: floating, - // ); - // } - // return !horizontalScreen || orientation == Orientation.portrait - // ? childWhenDisabled - // : childWhenDisabledLandscape; }); } } diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 6346c9ab1..03f9fe7d9 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -1467,7 +1467,7 @@ class _HeaderControlState extends State { Widget shootDanmakuButton() { return SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: '发弹幕', style: ButtonStyle( @@ -1486,7 +1486,7 @@ class _HeaderControlState extends State { Widget danmakuSwitcher() { return SizedBox( width: 42, - height: 34, + height: 38, child: Obx( () => IconButton( tooltip: "${widget.controller!.isOpenDanmu.value ? '关闭' : '开启'}弹幕", @@ -1517,7 +1517,7 @@ class _HeaderControlState extends State { Widget pipButton() { return SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: '画中画', style: ButtonStyle( @@ -1557,7 +1557,7 @@ class _HeaderControlState extends State { Widget likeVideoButton() { return SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: videoIntroController.hasLike.value ? '已点赞' : '点赞', style: ButtonStyle( @@ -1580,7 +1580,7 @@ class _HeaderControlState extends State { Widget coinVideoButton() { return SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: videoIntroController.hasCoin.value ? '已投币' : '投币', style: ButtonStyle( @@ -1603,7 +1603,7 @@ class _HeaderControlState extends State { Widget shareButton() { return SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: '分享', style: ButtonStyle( @@ -1624,10 +1624,9 @@ class _HeaderControlState extends State { @override Widget build(BuildContext context) { return Obx(() { - bool equivalentFullScreen() => - !widget.controller!.isFullScreen.value && - !horizontalScreen && - MediaQuery.of(context).orientation == Orientation.landscape; + bool isEquivalentFullScreen = widget.controller!.isFullScreen.value || + !widget.controller!.horizontalScreen && + MediaQuery.of(context).orientation == Orientation.landscape; return AppBar( backgroundColor: Colors.transparent, foregroundColor: Colors.white, @@ -1637,16 +1636,14 @@ class _HeaderControlState extends State { centerTitle: false, automaticallyImplyLeading: false, titleSpacing: 10, - toolbarHeight: - widget.controller!.isFullScreen.value || equivalentFullScreen() - ? 100 - : null, + toolbarHeight: isEquivalentFullScreen ? 100 : null, title: Column(mainAxisAlignment: MainAxisAlignment.start, children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: '上一页', icon: const Icon( @@ -1669,7 +1666,7 @@ class _HeaderControlState extends State { ), SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: '返回主页', icon: const Icon( @@ -1690,8 +1687,7 @@ class _HeaderControlState extends State { ), const SizedBox(width: 10), if ((videoIntroController.videoDetail.value.title != null) && - (widget.controller!.isFullScreen.value || - equivalentFullScreen())) + isEquivalentFullScreen) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1702,7 +1698,8 @@ class _HeaderControlState extends State { color: Colors.white, fontSize: 16, ), - maxLines: 2, + overflow: TextOverflow.ellipsis, + maxLines: 1, ), if (videoIntroController.isShowOnlineTotal) Text( @@ -1714,9 +1711,7 @@ class _HeaderControlState extends State { ) ], ), - ) - else - const Spacer(), + ), // ComBtn( // icon: const Icon( // FontAwesomeIcons.cropSimple, @@ -1725,15 +1720,16 @@ class _HeaderControlState extends State { // ), // fuc: () => _.screenshot(), // ), - if (!widget.controller!.isFullScreen.value && - !equivalentFullScreen()) ...[ + if (!isEquivalentFullScreen) ...[ + const SizedBox(width: 42), + const SizedBox(width: 42), shootDanmakuButton(), danmakuSwitcher(), pipButton(), ], SizedBox( width: 42, - height: 34, + height: 38, child: IconButton( tooltip: "更多设置", style: ButtonStyle( @@ -1757,7 +1753,7 @@ class _HeaderControlState extends State { // if ((isFullScreen || !horizontalScreen)) // const Spacer(), // show current datetime - if (widget.controller!.isFullScreen.value || equivalentFullScreen()) + if (isEquivalentFullScreen) Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Obx( () => Text(" ${now.value}", @@ -1769,8 +1765,7 @@ class _HeaderControlState extends State { semanticsLabel: nowSemanticsLabel), ), const SizedBox(width: 1.5), - if (widget.controller!.isFullScreen.value) - const SizedBox(width: 42), + if (isEquivalentFullScreen) const SizedBox(width: 42), for (var i = 0; i < 11; i++) const SizedBox(width: 0), likeVideoButton(), coinVideoButton(), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index dd21ba881..09c3b9232 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -278,6 +278,7 @@ class PlPlayerController { // int? defaultDuration; late bool enableAutoLongPressSpeed = false; late bool enableLongShowControl; + late bool horizontalScreen; // 播放顺序相关 PlayRepeat playRepeat = PlayRepeat.pause; @@ -324,6 +325,11 @@ class PlPlayerController { return _instance != null; } + static void updateSettings() { + _instance?.horizontalScreen = + setting.get(SettingBoxKey.horizontalScreen, defaultValue: false); + } + static Future playIfExists( {bool repeat = false, bool hideControls = true}) async { await _instance?.play(repeat: repeat, hideControls: hideControls); @@ -360,19 +366,24 @@ class PlPlayerController { isOpenDanmu.value = setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: true); blockTypes = setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []); - showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5); + showArea = setting + .get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5) + .toDouble(); // 不透明度 - opacityVal = setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0); + opacityVal = + setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0).toDouble(); // 字体大小 - fontSizeVal = - setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0); + fontSizeVal = setting + .get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0) + .toDouble(); // 弹幕时间 danmakuDurationVal = setting.get(SettingBoxKey.danmakuDuration, defaultValue: 7.29).round(); // 描边粗细 - strokeWidth = setting.get(SettingBoxKey.strokeWidth, defaultValue: 1.5); + strokeWidth = + setting.get(SettingBoxKey.strokeWidth, defaultValue: 1.5).toDouble(); // 弹幕字体粗细 - fontWeight = setting.get(SettingBoxKey.fontWeight, defaultValue: 5); + fontWeight = setting.get(SettingBoxKey.fontWeight, defaultValue: 5).round(); // 弹幕海量模式 massiveMode = setting.get(SettingBoxKey.danmakuMassiveMode, defaultValue: false); @@ -382,8 +393,9 @@ class PlPlayerController { videoStorage.get(VideoBoxKey.playRepeat, defaultValue: PlayRepeat.pause.value), ); - _playbackSpeed.value = - videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); + _playbackSpeed.value = videoStorage + .get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0) + .toDouble(); enableAutoLongPressSpeed = setting .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); // 后台播放 @@ -391,13 +403,18 @@ class PlPlayerController { .get(SettingBoxKey.continuePlayInBackground, defaultValue: false); if (!enableAutoLongPressSpeed) { _longPressSpeed.value = videoStorage - .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0); + .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0) + .toDouble(); } enableLongShowControl = setting.get(SettingBoxKey.enableLongShowControl, defaultValue: false); - speedsList = List.from(videoStorage.get( - VideoBoxKey.customSpeedsList, - defaultValue: [0.5, 0.75, 1.25, 1.5, 1.75, 3.0])); + horizontalScreen = + setting.get(SettingBoxKey.horizontalScreen, defaultValue: false); + + List defaultList = [0.5, 0.75, 1.25, 1.5, 1.75, 3.0]; + speedsList = List.from(videoStorage + .get(VideoBoxKey.customSpeedsList, defaultValue: defaultList) + .map((e) => e.toDouble())); for (final PlaySpeed i in PlaySpeed.values) { speedsList.add(i.value); } @@ -1442,7 +1459,8 @@ class PlPlayerController { } // 全屏 - Future triggerFullScreen({bool status = true}) async { + Future triggerFullScreen( + {bool status = true, bool equivalent = false}) async { stopScreenTimer(); FullScreenMode mode = FullScreenModeCode.fromCode( setting.get(SettingBoxKey.fullScreenMode, defaultValue: 0))!; @@ -1477,10 +1495,11 @@ class PlPlayerController { // StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); if (!removeSafeArea) showStatusBar(); toggleFullScreen(false); + await Future.delayed(const Duration(milliseconds: 10)); if (mode == FullScreenMode.none) { return; } - if (!setting.get(SettingBoxKey.horizontalScreen, defaultValue: false)) { + if (!horizontalScreen) { await verticalScreenForTwoSeconds(); } else { await autoScreen(); diff --git a/lib/plugin/pl_player/models/bottom_control_type.dart b/lib/plugin/pl_player/models/bottom_control_type.dart index c74b77ba1..affb53b91 100644 --- a/lib/plugin/pl_player/models/bottom_control_type.dart +++ b/lib/plugin/pl_player/models/bottom_control_type.dart @@ -2,7 +2,7 @@ enum BottomControlType { pre, playOrPause, next, - time, + // time, space, spaceButton, episode, diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 868986ace..696abbc92 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -306,11 +306,14 @@ class _PLVideoPlayerState extends State videoIntroController!.videoDetail.value.pages!.length > 1; bool isBangumi = bangumiIntroController?.bangumiDetail.value != null; bool anySeason = isSeason || isPage || isBangumi; + bool isEquivalentFullScreen = _.isFullScreen.value || + !_.horizontalScreen && + MediaQuery.of(context).orientation == Orientation.landscape; Map videoProgressWidgets = { /// 上一集 BottomControlType.pre: Container( width: 42, - height: 30, + height: 38, alignment: Alignment.center, child: ComBtn( icon: const Icon( @@ -341,7 +344,7 @@ class _PLVideoPlayerState extends State /// 下一集 BottomControlType.next: Container( width: 42, - height: 30, + height: 38, alignment: Alignment.center, child: ComBtn( icon: const Icon( @@ -365,38 +368,38 @@ class _PLVideoPlayerState extends State ), /// 时间进度 - BottomControlType.time: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - // 播放时间 - Obx(() { - return Text( - Utils.timeFormat(_.positionSeconds.value), - style: const TextStyle( - color: Colors.white, - fontSize: 10, - height: 1.4, - fontFeatures: [FontFeature.tabularFigures()], - ), - semanticsLabel: - '已播放${Utils.durationReadFormat(Utils.timeFormat(_.positionSeconds.value))}', - ); - }), - Obx( - () => Text( - Utils.timeFormat(_.durationSeconds.value), - style: const TextStyle( - color: Color(0xFFD0D0D0), - fontSize: 10, - height: 1.4, - fontFeatures: [FontFeature.tabularFigures()], - ), - semanticsLabel: - '共${Utils.durationReadFormat(Utils.timeFormat(_.durationSeconds.value))}', - ), - ), - ], - ), + // BottomControlType.time: Column( + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [ + // // 播放时间 + // Obx(() { + // return Text( + // Utils.timeFormat(_.positionSeconds.value), + // style: const TextStyle( + // color: Colors.white, + // fontSize: 10, + // height: 1.4, + // fontFeatures: [FontFeature.tabularFigures()], + // ), + // semanticsLabel: + // '已播放${Utils.durationReadFormat(Utils.timeFormat(_.positionSeconds.value))}', + // ); + // }), + // Obx( + // () => Text( + // Utils.timeFormat(_.durationSeconds.value), + // style: const TextStyle( + // color: Color(0xFFD0D0D0), + // fontSize: 10, + // height: 1.4, + // fontFeatures: [FontFeature.tabularFigures()], + // ), + // semanticsLabel: + // '共${Utils.durationReadFormat(Utils.timeFormat(_.durationSeconds.value))}', + // ), + // ), + // ], + // ), /// 空白占位 BottomControlType.space: const SizedBox(width: 0), @@ -405,7 +408,7 @@ class _PLVideoPlayerState extends State /// 选集 BottomControlType.episode: Container( width: 42, - height: 30, + height: 38, alignment: Alignment.center, child: ComBtn( icon: const Icon( @@ -452,7 +455,7 @@ class _PLVideoPlayerState extends State /// 画面比例 BottomControlType.fit: SizedBox( width: 42, - height: 30, + height: 38, child: TextButton( onPressed: () => _.toggleVideoFit(), style: ButtonStyle( @@ -470,10 +473,10 @@ class _PLVideoPlayerState extends State /// 字幕 BottomControlType.subtitle: Obx( () => _.vttSubtitles.isEmpty - ? const SizedBox(width: 42, height: 30) + ? const SizedBox(width: 42, height: 38) : SizedBox( width: 42, - height: 30, + height: 38, child: PopupMenuButton( onSelected: (int value) { _.setSubtitle(value); @@ -496,7 +499,7 @@ class _PLVideoPlayerState extends State }, child: Container( width: 42, - height: 30, + height: 38, alignment: Alignment.center, child: const Icon( Icons.closed_caption, @@ -512,7 +515,7 @@ class _PLVideoPlayerState extends State /// 播放速度 BottomControlType.speed: SizedBox( width: 42, - height: 30, + height: 38, child: PopupMenuButton( onSelected: (double value) { _.setPlaybackSpeed(value); @@ -522,7 +525,7 @@ class _PLVideoPlayerState extends State itemBuilder: (BuildContext context) { return _.speedsList.map((double speed) { return PopupMenuItem( - height: 35, + height: 38, padding: const EdgeInsets.only(left: 30), value: speed, child: Text( @@ -535,7 +538,7 @@ class _PLVideoPlayerState extends State }, child: Container( width: 42, - height: 30, + height: 38, alignment: Alignment.center, child: Obx(() => Text("${_.playbackSpeed}X", style: const TextStyle(color: Colors.white, fontSize: 13), @@ -547,7 +550,7 @@ class _PLVideoPlayerState extends State /// 全屏 BottomControlType.fullscreen: SizedBox( width: 42, - height: 30, + height: 38, child: Obx(() => ComBtn( icon: Icon( _.isFullScreen.value ? Icons.fullscreen_exit : Icons.fullscreen, @@ -569,10 +572,10 @@ class _PLVideoPlayerState extends State for (var i = 0; i < 12; i++) BottomControlType.space, if (!anySeason) for (var i = 0; i < 3; i++) BottomControlType.spaceButton, - // if (!_.isFullScreen.value) BottomControlType.spaceButton, + // if (!isEquivalentFullScreen) BottomControlType.spaceButton, BottomControlType.subtitle, if (anySeason) BottomControlType.episode, - if (_.isFullScreen.value) BottomControlType.fit, + if (isEquivalentFullScreen) BottomControlType.fit, BottomControlType.speed, BottomControlType.fullscreen, ]; @@ -1097,6 +1100,10 @@ class _PLVideoPlayerState extends State final int value = _.sliderPositionSeconds.value; final int max = _.durationSeconds.value; final int buffer = _.bufferedSeconds.value; + + bool isEquivalentFullScreen = _.isFullScreen.value || + !_.horizontalScreen && + MediaQuery.of(context).orientation == Orientation.landscape; if (_.showControls.value) { return Container(); } @@ -1106,11 +1113,11 @@ class _PLVideoPlayerState extends State } if (defaultBtmProgressBehavior == BtmProgressBehavior.onlyShowFullScreen.code && - !_.isFullScreen.value) { + !isEquivalentFullScreen) { return const SizedBox(); } else if (defaultBtmProgressBehavior == BtmProgressBehavior.onlyHideFullScreen.code && - _.isFullScreen.value) { + isEquivalentFullScreen) { return const SizedBox(); } @@ -1185,17 +1192,19 @@ class _PLVideoPlayerState extends State ), // 锁 - SafeArea( - child: Obx( - () => Align( + SafeArea(child: Obx(() { + bool isEquivalentFullScreen = _.isFullScreen.value || + !_.horizontalScreen && + MediaQuery.of(context).orientation == Orientation.landscape; + return Align( alignment: Alignment.centerLeft, child: FractionalTranslation( - translation: const Offset(1, -0.4), + translation: const Offset(1, -0.2), child: Visibility( visible: _.videoType.value != 'live' && enableExtraButtonOnFullScreen && _.showControls.value && - (_.isFullScreen.value || _.controlsLock.value), + (isEquivalentFullScreen || _.controlsLock.value), child: ComBtn( icon: Icon( _.controlsLock.value @@ -1209,19 +1218,21 @@ class _PLVideoPlayerState extends State ), ), ), - ), - )), + ); + })), // 截图 - SafeArea( - child: Obx( - () => Align( + SafeArea(child: Obx(() { + bool isEquivalentFullScreen = _.isFullScreen.value || + !_.horizontalScreen && + MediaQuery.of(context).orientation == Orientation.landscape; + return Align( alignment: Alignment.centerRight, child: FractionalTranslation( - translation: const Offset(-1, -0.4), + translation: const Offset(-1, -0.2), child: Visibility( visible: _.showControls.value && - _.isFullScreen.value && + isEquivalentFullScreen && enableExtraButtonOnFullScreen, child: ComBtn( icon: const Icon( @@ -1286,8 +1297,8 @@ class _PLVideoPlayerState extends State ), ), ), - ), - )), + ); + })), // Obx(() { if (_.dataStatus.loading || _.isBuffering.value) { diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 1575c6eb8..451ddf0fc 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -6,6 +6,8 @@ import 'package:get/get.dart'; import 'package:nil/nil.dart'; import 'package:PiliPalaX/plugin/pl_player/index.dart'; import 'package:PiliPalaX/utils/feed_back.dart'; +//dart-math +import 'dart:math' as math; import '../../../common/widgets/audio_video_progress_bar.dart'; @@ -28,82 +30,84 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { //阅读器限制 Timer? accessibilityDebounce; double lastAnnouncedValue = -1; - return Container( - color: Colors.transparent, - height: 70 + (_.isFullScreen.value ? Get.height * 0.08 : 0), - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Obx( - () { - final int value = _.sliderPositionSeconds.value; - final int max = _.durationSeconds.value; - final int buffer = _.bufferedSeconds.value; - if (value > max || max <= 0) { - return nil; - } - return Padding( - padding: EdgeInsets.only( - left: 10, - right: 10, - bottom: 5 + (_.isFullScreen.value ? Get.height * 0.01 : 0)), - child: Semantics( - // label: '${(value / max * 100).round()}%', - value: '${(value / max * 100).round()}%', - // enabled: false, - child: ProgressBar( - progress: Duration(seconds: value), - buffered: Duration(seconds: buffer), - total: Duration(seconds: max), - progressBarColor: colorTheme, - baseBarColor: Colors.white.withOpacity(0.2), - bufferedBarColor: colorTheme.withOpacity(0.4), - timeLabelLocation: TimeLabelLocation.sides, - timeLabelTextStyle: const TextStyle(color: Colors.white), - // timeLabelLocation: TimeLabelLocation.none, - thumbColor: colorTheme, - barHeight: 3.5, - thumbRadius: 7, - onDragStart: (duration) { - feedBack(); - _.onChangedSliderStart(); - }, - onDragUpdate: (duration) { - double newProgress = duration.timeStamp.inSeconds / max; - if ((newProgress - lastAnnouncedValue).abs() > 0.02) { - accessibilityDebounce?.cancel(); - accessibilityDebounce = - Timer(const Duration(milliseconds: 200), () { - SemanticsService.announce( - "${(newProgress * 100).round()}%", - TextDirection.ltr); - lastAnnouncedValue = newProgress; - }); - } - _.onUpdatedSliderProgress(duration.timeStamp); - }, - onSeek: (duration) { - _.onChangedSliderEnd(); - _.onChangedSlider(duration.inSeconds.toDouble()); - _.seekTo(Duration(seconds: duration.inSeconds), - type: 'slider'); - SemanticsService.announce( - "${(duration.inSeconds / max * 100).round()}%", - TextDirection.ltr); - }, - )), - ); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [...buildBottomControl!], - ), - const SizedBox(height: 9), - if (_.isFullScreen.value) SizedBox(height: Get.height * 0.07), - ], - ), - ); + return Obx(() { + final int value = _.sliderPositionSeconds.value; + final int max = _.durationSeconds.value; + final int buffer = _.bufferedSeconds.value; + if (value > max || max <= 0) { + return nil; + } + bool isEquivalentFullScreen = _.isFullScreen.value || + !_.horizontalScreen && + MediaQuery.of(context).orientation == Orientation.landscape; + return Container( + color: Colors.transparent, + height: 70 + (isEquivalentFullScreen ? Get.height * 0.08 : 0), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only( + left: 10, + right: 10, + bottom: 3 + (isEquivalentFullScreen ? Get.height * 0.01 : 0)), + child: Semantics( + // label: '${(value / max * 100).round()}%', + value: '${(value / max * 100).round()}%', + // enabled: false, + child: ProgressBar( + progress: Duration(seconds: value), + buffered: Duration(seconds: buffer), + total: Duration(seconds: max), + progressBarColor: colorTheme, + baseBarColor: Colors.white.withOpacity(0.2), + bufferedBarColor: colorTheme.withOpacity(0.4), + timeLabelLocation: TimeLabelLocation.sides, + timeLabelTextStyle: const TextStyle(color: Colors.white), + // timeLabelLocation: TimeLabelLocation.none, + thumbColor: colorTheme, + barHeight: 3.5, + thumbRadius: 7, + onDragStart: (duration) { + feedBack(); + _.onChangedSliderStart(); + }, + onDragUpdate: (duration) { + double newProgress = duration.timeStamp.inSeconds / max; + if ((newProgress - lastAnnouncedValue).abs() > 0.02) { + accessibilityDebounce?.cancel(); + accessibilityDebounce = + Timer(const Duration(milliseconds: 200), () { + SemanticsService.announce( + "${(newProgress * 100).round()}%", + TextDirection.ltr); + lastAnnouncedValue = newProgress; + }); + } + _.onUpdatedSliderProgress(duration.timeStamp); + }, + onSeek: (duration) { + _.onChangedSliderEnd(); + _.onChangedSlider(duration.inSeconds.toDouble()); + _.seekTo(Duration(seconds: duration.inSeconds), + type: 'slider'); + SemanticsService.announce( + "${(duration.inSeconds / max * 100).round()}%", + TextDirection.ltr); + }, + )), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [...buildBottomControl!], + ), + const SizedBox(height: 6), + if (isEquivalentFullScreen) + SizedBox(height: math.max(Get.height * 0.08 - 15, 0)), + ], + ), + ); + }); } } diff --git a/lib/plugin/pl_player/widgets/play_pause_btn.dart b/lib/plugin/pl_player/widgets/play_pause_btn.dart index 0bf00c54d..d892f2927 100644 --- a/lib/plugin/pl_player/widgets/play_pause_btn.dart +++ b/lib/plugin/pl_player/widgets/play_pause_btn.dart @@ -65,7 +65,7 @@ class PlayOrPauseButtonState extends State Widget build(BuildContext context) { return SizedBox( width: 42, - height: 34, + height: 38, child: InkWell( onTap: player.playOrPause, // iconSize: widget.iconSize ?? _theme(context).buttonBarButtonSize, diff --git a/lib/utils/cache_manage.dart b/lib/utils/cache_manage.dart index a67ec7062..8fb6d6f47 100644 --- a/lib/utils/cache_manage.dart +++ b/lib/utils/cache_manage.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'package:PiliPalaX/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -83,38 +84,58 @@ class CacheManage { // 清除缓存 Future clearCacheAll(BuildContext context) async { + // 是否启动时清除 + RxBool autoClearCache = RxBool(GStorage.setting + .get(SettingBoxKey.autoClearCache, defaultValue: false)); bool cleanStatus = await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('提示'), - content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'), + content: const Text('该操作将清除图片及网络请求缓存数据'), actions: [ + Obx( + () => TextButton.icon( + onPressed: () { + autoClearCache.value = !autoClearCache.value; + GStorage.setting + .put(SettingBoxKey.autoClearCache, autoClearCache.value); + SmartDialog.showToast( + autoClearCache.value ? '启动时自动清除缓存' : '已关闭'); + }, + icon: Icon(autoClearCache.value + ? Icons.check_box + : Icons.check_box_outline_blank), + label: const Text( + '自动', + ), + ), + ), TextButton( - onPressed: () => Get.back(), + onPressed: () { + Get.back(); + }, child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), ), ), TextButton( + autofocus: true, onPressed: () async { Get.back(); SmartDialog.showLoading(msg: '正在清除...'); try { - // 清除缓存 图片缓存 await clearLibraryCache(); - Timer(const Duration(milliseconds: 500), () { - SmartDialog.dismiss().then((res) { - SmartDialog.showToast('清除成功'); - }); + SmartDialog.dismiss().then((res) { + SmartDialog.showToast('清除成功'); }); } catch (err) { SmartDialog.dismiss(); SmartDialog.showToast(err.toString()); } }, - child: const Text('确认'), + child: const Text('清除'), ) ], ); diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 744b36c86..cd69ab498 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -50,9 +50,29 @@ class GStorage { setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); // 设置全局变量 } + // 特殊处理playerGestureActionMap的逻辑 + // json不支持Map,需要使用Map中介 + static String specialKey = SettingBoxKey.playerGestureActionMap; + + static Map toEncodableManually(Map data) { + if (data.containsKey(specialKey)) { + data[specialKey] = + data[specialKey].map((key, value) => MapEntry(key.toString(), value)); + } + return data; + } + + static Map fromEncodableManually(Map data) { + if (data.containsKey(specialKey)) { + data[specialKey] = + data[specialKey].map((key, value) => MapEntry(int.parse(key), value)); + } + return data; + } + static Future exportAllSettings() async { return jsonEncode({ - setting.name: setting.toMap(), + setting.name: toEncodableManually(setting.toMap()), video.name: video.toMap(), }); } @@ -61,7 +81,7 @@ class GStorage { final Map map = jsonDecode(data); await setting.clear(); await video.clear(); - await setting.putAll(map[setting.name]); + await setting.putAll(fromEncodableManually(map[setting.name])); await video.putAll(map[video.name]); }