diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index 1bfb9d455..75a29e318 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -2,7 +2,6 @@ import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/models/model_hot_video_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import '../../http/search.dart'; import '../../utils/utils.dart'; import '../constants.dart'; @@ -88,8 +87,8 @@ class VideoCardH extends StatelessWidget { try { final int cid = videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'videoItem': videoItem, 'heroTag': Utils.makeHeroTag(aid) diff --git a/lib/common/widgets/video_card_h_member_video.dart b/lib/common/widgets/video_card_h_member_video.dart index 74c856403..7108a015c 100644 --- a/lib/common/widgets/video_card_h_member_video.dart +++ b/lib/common/widgets/video_card_h_member_video.dart @@ -6,7 +6,6 @@ import 'package:PiliPlus/common/widgets/video_progress_indicator.dart'; import 'package:PiliPlus/models/space_archive/item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import '../../utils/utils.dart'; import '../constants.dart'; import 'badge.dart'; @@ -48,8 +47,8 @@ class VideoCardHMemberVideo extends StatelessWidget { return; } try { - Get.toNamed( - '/video?bvid=${videoItem.bvid}&cid=${videoItem.firstCid}', + Utils.toViewPage( + 'bvid=${videoItem.bvid}&cid=${videoItem.firstCid}', arguments: { 'heroTag': Utils.makeHeroTag(videoItem.bvid), }, diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 6754dfd69..ee0b93dec 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -43,8 +43,8 @@ class VideoCardV extends StatelessWidget { if (cid == -1) { cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid); } - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { // 'videoItem': videoItem, 'pic': videoItem.pic, diff --git a/lib/common/widgets/video_card_v_member_home.dart b/lib/common/widgets/video_card_v_member_home.dart index 0db74cd9c..e9bac0f81 100644 --- a/lib/common/widgets/video_card_v_member_home.dart +++ b/lib/common/widgets/video_card_v_member_home.dart @@ -2,7 +2,6 @@ import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:PiliPlus/models/space/item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import '../../utils/utils.dart'; import '../constants.dart'; import 'badge.dart'; @@ -30,8 +29,8 @@ class VideoCardVMemberHome extends StatelessWidget { } } String bvid = videoItem.bvid ?? ''; - Get.toNamed( - '/video?bvid=$bvid&cid=${videoItem.firstCid}', + Utils.toViewPage( + 'bvid=$bvid&cid=${videoItem.firstCid}', arguments: { // 'videoItem': videoItem, 'pic': videoItem.cover, diff --git a/lib/pages/bangumi/widgets/bangumi_card_v.dart b/lib/pages/bangumi/widgets/bangumi_card_v.dart index fe9349a3b..f9b35721d 100644 --- a/lib/pages/bangumi/widgets/bangumi_card_v.dart +++ b/lib/pages/bangumi/widgets/bangumi_card_v.dart @@ -29,45 +29,6 @@ class BangumiCardV extends StatelessWidget { onTap: () async { final int seasonId = bangumiItem.seasonId; Utils.viewBangumi(seasonId: seasonId); - // SmartDialog.showLoading(msg: '获取中...'); - // final res = await SearchHttp.bangumiInfo(seasonId: seasonId); - // SmartDialog.dismiss().then((value) { - // if (res['status']) { - // if (res['data'].episodes.isEmpty) { - // SmartDialog.showToast('资源加载失败'); - // return; - // } - // EpisodeItem episode = res['data'].episodes.first; - // int? epId = res['data'].userStatus?.progress?.lastEpId; - // if (epId == null) { - // epId = episode.epId; - // } else { - // for (var item in res['data'].episodes) { - // if (item.epId == epId) { - // episode = item; - // break; - // } - // } - // } - // String bvid = episode.bvid!; - // int cid = episode.cid!; - // String pic = episode.cover!; - // // debugPrint('epId'); - // // debugPrint(epId); - // String heroTag = Utils.makeHeroTag(cid); - // Get.toNamed( - // '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId', - // arguments: { - // 'pic': pic, - // 'heroTag': heroTag, - // 'videoType': SearchType.media_bangumi, - // 'bangumiItem': res['data'], - // }, - // ); - // } else { - // SmartDialog.showToast(res['msg']); - // } - // }); }, child: Column( children: [ diff --git a/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart b/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart index 4755af020..f1930eacd 100644 --- a/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart +++ b/lib/pages/bangumi/widgets/bangumi_card_v_member_home.dart @@ -24,45 +24,6 @@ class BangumiCardVMemberHome extends StatelessWidget { onTap: () async { final int seasonId = int.tryParse(bangumiItem.param ?? '') ?? -1; Utils.viewBangumi(seasonId: seasonId); - // SmartDialog.showLoading(msg: '获取中...'); - // final res = await SearchHttp.bangumiInfo(seasonId: seasonId); - // SmartDialog.dismiss().then((value) { - // if (res['status']) { - // if (res['data'].episodes.isEmpty) { - // SmartDialog.showToast('资源加载失败'); - // return; - // } - // EpisodeItem episode = res['data'].episodes.first; - // int? epId = res['data'].userStatus?.progress?.lastEpId; - // if (epId == null) { - // epId = episode.epId; - // } else { - // for (var item in res['data'].episodes) { - // if (item.epId == epId) { - // episode = item; - // break; - // } - // } - // } - // String bvid = episode.bvid!; - // int cid = episode.cid!; - // String pic = episode.cover!; - // // debugPrint('epId'); - // // debugPrint(epId); - // String heroTag = Utils.makeHeroTag(cid); - // Get.toNamed( - // '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId', - // arguments: { - // 'pic': pic, - // 'heroTag': heroTag, - // 'videoType': SearchType.media_bangumi, - // 'bangumiItem': res['data'], - // }, - // ); - // } else { - // SmartDialog.showToast(res['msg']); - // } - // }); }, onLongPress: () => imageSaveDialog( context: context, diff --git a/lib/pages/dynamics/widgets/additional_panel.dart b/lib/pages/dynamics/widgets/additional_panel.dart index 709d2d041..318941d4d 100644 --- a/lib/pages/dynamics/widgets/additional_panel.dart +++ b/lib/pages/dynamics/widgets/additional_panel.dart @@ -1,7 +1,6 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/http/search.dart'; @@ -35,8 +34,8 @@ Widget addWidget(item, context, type, {floor = 1}) { String cover = dynamicProperty[type].cover; try { int cid = await SearchHttp.ab2c(bvid: bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': cover, 'heroTag': Utils.makeHeroTag(bvid), diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index c99b4da8f..7bbe65794 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -238,8 +238,8 @@ InlineSpan? richNode(item, context) { onTap: () async { try { int cid = await SearchHttp.ab2c(bvid: i.rid); - Get.toNamed( - '/video?bvid=${i.rid}&cid=$cid', + Utils.toViewPage( + 'bvid=${i.rid}&cid=$cid', arguments: { 'pic': null, 'heroTag': Utils.makeHeroTag(i.rid), diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 123f4c64e..11319335d 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -145,8 +145,8 @@ class FavDetailController extends MultiSelectController { if (element.bvid != list.first.bvid) { SmartDialog.showToast('已跳过不支持播放的视频'); } - Get.toNamed( - '/video?bvid=${element.bvid}&cid=${element.cid}', + Utils.toViewPage( + 'bvid=${element.bvid}&cid=${element.cid}', arguments: { 'videoItem': element, 'heroTag': Utils.makeHeroTag(element.bvid), diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 7989f1a11..acd100581 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -67,8 +67,8 @@ class FavVideoCardH extends StatelessWidget { // if (seasonId != null) { // parameters['seasonId'] = seasonId.toString(); // } - Get.toNamed( - '/video', + Utils.toViewPage( + '', parameters: parameters, arguments: { 'videoItem': videoItem, diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 9eb6813e9..3ee671f92 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -89,8 +89,8 @@ class HistoryItem extends StatelessWidget { } else { int cid = videoItem.history.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'heroTag': Utils.makeHeroTag(cid), 'pic': videoItem.cover, @@ -103,49 +103,14 @@ class HistoryItem extends StatelessWidget { } else { if (videoItem.history.epid != '') { Utils.viewBangumi(epId: videoItem.history.epid); - // SmartDialog.showLoading(msg: '获取中...'); - // var res = - // await SearchHttp.bangumiInfo(epId: videoItem.history.epid); - // SmartDialog.dismiss(); - // if (res['status']) { - // var bangumiDetail = res['data']; - // EpisodeItem episode = res['data'].episodes.first; - // int? epId = res['data'].userStatus?.progress?.lastEpId; - // if (epId == null) { - // epId = episode.epId; - // } else { - // for (var item in res['data'].episodes) { - // if (item.epId == epId) { - // episode = item; - // break; - // } - // } - // } - // String bvid = episode.bvid!; - // int cid = episode.cid!; - // String pic = episode.cover!; - // String seasonId = bangumiDetail.seasonId; - // dynamic heroTag = Utils.makeHeroTag(cid); - // Get.toNamed( - // '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId', - // arguments: { - // 'pic': pic, - // 'heroTag': heroTag, - // 'videoType': SearchType.media_bangumi, - // 'bangumiItem': res['data'], - // }, - // ); - // } else { - // SmartDialog.showToast(res['msg']); - // } } } } else { int cid = videoItem.history.cid ?? // videoItem.history.oid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'heroTag': Utils.makeHeroTag(aid), 'pic': videoItem.cover, diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index fca80a1c3..55e4728f5 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -164,8 +164,8 @@ class LaterController extends MultiSelectController { if (item.bvid != list.first.bvid) { SmartDialog.showToast('已跳过不支持播放的视频'); } - Get.toNamed( - '/video?bvid=${item.bvid}&cid=${item.cid}', + Utils.toViewPage( + 'bvid=${item.bvid}&cid=${item.cid}', arguments: { 'videoItem': item, 'heroTag': Utils.makeHeroTag(item.bvid), diff --git a/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart b/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart index 2f30633d6..caf61dc98 100644 --- a/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart +++ b/lib/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart @@ -104,8 +104,8 @@ class MemberVideoCtr extends CommonController { ?.group(1); dynamic bvid = IdUtils.av2bv(int.tryParse(oid) ?? 0); dynamic cid = await SearchHttp.ab2c(aid: oid, bvid: bvid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'heroTag': Utils.makeHeroTag(oid), 'sourceType': 'archive', @@ -145,8 +145,8 @@ class MemberVideoCtr extends CommonController { : sort.value == 'asc') ? desc.not : desc; - Get.toNamed( - '/video?bvid=${element.bvid}&cid=${element.firstCid}', + Utils.toViewPage( + 'bvid=${element.bvid}&cid=${element.firstCid}', arguments: { 'videoItem': element, 'heroTag': Utils.makeHeroTag(element.bvid), diff --git a/lib/pages/member_coin/widgets/item.dart b/lib/pages/member_coin/widgets/item.dart index dc8112913..7d4aa5899 100644 --- a/lib/pages/member_coin/widgets/item.dart +++ b/lib/pages/member_coin/widgets/item.dart @@ -1,6 +1,5 @@ import 'package:PiliPlus/common/widgets/image_save.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; @@ -26,8 +25,8 @@ class MemberCoinsItem extends StatelessWidget { onTap: () async { int cid = await SearchHttp.ab2c(aid: coinItem.aid, bvid: coinItem.bvid); - Get.toNamed( - '/video?bvid=${coinItem.bvid}&cid=$cid', + Utils.toViewPage( + 'bvid=${coinItem.bvid}&cid=$cid', arguments: { 'videoItem': coinItem, 'heroTag': Utils.makeHeroTag(coinItem.aid) diff --git a/lib/pages/member_search/search_dynamic.dart b/lib/pages/member_search/search_dynamic.dart index 209d90515..ed6dac436 100644 --- a/lib/pages/member_search/search_dynamic.dart +++ b/lib/pages/member_search/search_dynamic.dart @@ -256,8 +256,8 @@ class SearchDynamic extends StatelessWidget { return ListTile( dense: true, onTap: () { - Get.toNamed( - '/video?bvid=${IdUtils.av2bv(json['aid'])}&cid=${json['cid']}', + Utils.toViewPage( + 'bvid=${IdUtils.av2bv(json['aid'])}&cid=${json['cid']}', arguments: { 'heroTag': Utils.makeHeroTag(json['aid']), }, diff --git a/lib/pages/member_seasons/widgets/item.dart b/lib/pages/member_seasons/widgets/item.dart index f3576f107..c4db60b9e 100644 --- a/lib/pages/member_seasons/widgets/item.dart +++ b/lib/pages/member_seasons/widgets/item.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; @@ -25,8 +24,8 @@ class MemberSeasonsItem extends StatelessWidget { onTap: () async { int cid = await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid); - Get.toNamed( - '/video?bvid=${seasonItem.bvid}&cid=$cid', + Utils.toViewPage( + 'bvid=${seasonItem.bvid}&cid=$cid', arguments: { 'videoItem': seasonItem, 'heroTag': Utils.makeHeroTag(seasonItem.aid) diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index e9a0f7cb3..075762715 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -2101,6 +2101,16 @@ List get extraSettings => [ setKey: SettingBoxKey.antiGoodsReply, defaultVal: false, ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '使用可折叠的播放页面', + leading: Icon(Icons.video_settings), + setKey: SettingBoxKey.collapsibleVideoPage, + defaultVal: false, + onChanged: (value) { + GStorage.collapsibleVideoPage = value; + }, + ), SettingsModel( settingsType: SettingsType.sw1tch, enableFeedback: true, diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index 91b67fe54..87ad7b3cb 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -1,5 +1,4 @@ import 'package:PiliPlus/common/widgets/image_save.dart'; -import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/stat/danmu.dart'; @@ -34,8 +33,8 @@ class SubVideoCardH extends StatelessWidget { 'cid': cid.toString(), }; - Get.toNamed( - '/video', + Utils.toViewPage( + '', parameters: parameters, arguments: { 'videoItem': videoItem, diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 01bca7fe9..17206731f 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -27,6 +27,7 @@ import 'package:PiliPlus/utils/extension.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:easy_debounce/easy_throttle.dart'; +import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -126,6 +127,9 @@ class VideoDetailController extends GetxController PlayerStatus? playerStatus; StreamSubscription? positionSubscription; + late final scrollKey = GlobalKey(); + late final RxString direction = 'horizontal'.obs; + bool imageStatus = false; void onViewImage() { @@ -1082,6 +1086,11 @@ class VideoDetailController extends GetxController baseUrl: videoUrl, codecs: 'avc1', quality: VideoQualityCode.fromCode(data.quality!)!); + direction.value = firstVideo.width != null && firstVideo.height != null + ? firstVideo.width! > firstVideo.height! + ? 'horizontal' + : 'vertical' + : 'horizontal'; currentDecodeFormats = VideoDecodeFormatsCode.fromString('avc1')!; currentVideoQa = VideoQualityCode.fromCode(data.quality!)!; if (autoPlay.value) { @@ -1152,6 +1161,11 @@ class VideoDetailController extends GetxController firstVideo = videosList.firstWhere( (e) => e.codecs!.startsWith(currentDecodeFormats.code), orElse: () => videosList.first); + direction.value = firstVideo.width != null && firstVideo.height != null + ? firstVideo.width! > firstVideo.height! + ? 'horizontal' + : 'vertical' + : 'horizontal'; // videoUrl = enableCDN // ? VideoUtils.getCdnUrl(firstVideo) diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 76994d7bc..7229b0652 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -821,13 +821,6 @@ class VideoIntroController extends GetxController (relatedCtr.loadingState.value as Success).response[0]; try { if (videoItem.cid != null) { - // Get.offNamed( - // '/video?bvid=${videoItem.bvid}&cid=${videoItem.cid}', - // arguments: { - // 'videoItem': videoItem, - // 'heroTag': heroTag, - // }, - // ); changeSeasonOrbangu( null, videoItem.bvid, @@ -837,12 +830,14 @@ class VideoIntroController extends GetxController ); } else { SearchHttp.ab2c(aid: videoItem.aid, bvid: videoItem.bvid).then( - (cid) => Get.offNamed( - '/video?bvid=${videoItem.bvid}&cid=${videoItem.cid}', - arguments: { - 'videoItem': videoItem, - 'heroTag': heroTag, - }), + (cid) => Utils.toViewPage( + 'bvid=${videoItem.bvid}&cid=${videoItem.cid}', + arguments: { + 'videoItem': videoItem, + 'heroTag': heroTag, + }, + off: true, + ), ); } } catch (err) { diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 0bbbc82db..9ed473dc1 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -305,8 +305,9 @@ class _VideoInfoState extends State with TickerProviderStateMixin { final ThemeData t = Theme.of(context); return SliverLayoutBuilder( builder: (BuildContext context, SliverConstraints constraints) { - bool isHorizontal = constraints.crossAxisExtent > - constraints.viewportMainAxisExtent * 1.25; + // bool isHorizontal = constraints.crossAxisExtent > + // constraints.viewportMainAxisExtent * 1.25; + bool isHorizontal = context.orientation == Orientation.landscape; return SliverPadding( padding: const EdgeInsets.only( left: StyleString.safeSpace, diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index 593b466d6..817e95183 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -6,8 +6,11 @@ import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:flutter/material.dart'; +import 'package:get/get_state_manager/src/rx_flutter/rx_ticket_provider_mixin.dart'; -class VideoReplyController extends ReplyController { +class VideoReplyController extends ReplyController + with GetTickerProviderStateMixin { VideoReplyController({required this.aid}); // 视频aid 请求时使用的oid int aid; @@ -15,6 +18,25 @@ class VideoReplyController extends ReplyController { @override dynamic get sourceId => IdUtils.av2bv(aid); + bool _isFabVisible = true; + late final AnimationController fabAnimationCtr = AnimationController( + vsync: this, duration: const Duration(milliseconds: 100)) + ..forward(); + + void showFab() { + if (!_isFabVisible) { + _isFabVisible = true; + fabAnimationCtr.forward(); + } + } + + void hideFab() { + if (_isFabVisible) { + _isFabVisible = false; + fabAnimationCtr.reverse(); + } + } + @override Future customGetData() => GlobalData().grpcReply ? ReplyHttp.replyListGrpc( @@ -34,4 +56,10 @@ class VideoReplyController extends ReplyController { page: currentPage, antiGoodsReply: antiGoodsReply, ); + + @override + void onClose() { + fabAnimationCtr.dispose(); + super.onClose(); + } } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 4ba0f9624..104ebe51f 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -26,6 +26,7 @@ class VideoReplyPanel extends StatefulWidget { final VoidCallback? onViewImage; final ValueChanged? onDismissed; final Function(List, int)? callback; + final bool? needController; const VideoReplyPanel({ super.key, @@ -38,6 +39,7 @@ class VideoReplyPanel extends StatefulWidget { this.onViewImage, this.onDismissed, this.callback, + this.needController, }); @override @@ -47,9 +49,7 @@ class VideoReplyPanel extends StatefulWidget { class _VideoReplyPanelState extends State with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { late VideoReplyController _videoReplyController; - late AnimationController fabAnimationCtr; - bool _isFabVisible = true; String replyLevel = '1'; late String heroTag; @@ -66,52 +66,33 @@ class _VideoReplyPanelState extends State replyLevel = widget.replyLevel ?? '1'; _videoReplyController = Get.find(tag: heroTag); - fabAnimationCtr = AnimationController( - vsync: this, duration: const Duration(milliseconds: 100)); - - fabAnimationCtr.forward(); - scrollListener(); + if (widget.needController != false) { + _videoReplyController.scrollController.addListener(listener); + } } @override void dispose() { - _videoReplyController.scrollController.removeListener(listener); - fabAnimationCtr.dispose(); + if (widget.needController != false) { + _videoReplyController.scrollController.removeListener(listener); + } super.dispose(); } - void scrollListener() { - _videoReplyController.scrollController.addListener(listener); - } - void listener() { final ScrollDirection direction = _videoReplyController.scrollController.position.userScrollDirection; if (direction == ScrollDirection.forward) { if (mounted) { - _showFab(); + _videoReplyController.showFab(); } } else if (direction == ScrollDirection.reverse) { if (mounted) { - _hideFab(); + _videoReplyController.hideFab(); } } } - void _showFab() { - if (!_isFabVisible) { - _isFabVisible = true; - fabAnimationCtr.forward(); - } - } - - void _hideFab() { - if (_isFabVisible) { - _isFabVisible = false; - fabAnimationCtr.reverse(); - } - } - @override Widget build(BuildContext context) { super.build(context); @@ -122,8 +103,14 @@ class _VideoReplyPanelState extends State child: Stack( children: [ CustomScrollView( - controller: _videoReplyController.scrollController, - physics: const AlwaysScrollableScrollPhysics(), + controller: widget.needController == false + ? null + : _videoReplyController.scrollController, + physics: widget.needController == false + ? const NeverScrollableScrollPhysics( + parent: ClampingScrollPhysics(), + ) + : const AlwaysScrollableScrollPhysics(), // key: const PageStorageKey('评论'), slivers: [ SliverPersistentHeader( @@ -182,7 +169,7 @@ class _VideoReplyPanelState extends State begin: const Offset(0, 2), end: const Offset(0, 0), ).animate(CurvedAnimation( - parent: fabAnimationCtr, + parent: _videoReplyController.fabAnimationCtr, curve: Curves.easeInOut, )), child: FloatingActionButton( diff --git a/lib/pages/video/detail/view_v.dart b/lib/pages/video/detail/view_v.dart new file mode 100644 index 000000000..9ea6f91ab --- /dev/null +++ b/lib/pages/video/detail/view_v.dart @@ -0,0 +1,2359 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/icon_button.dart'; +import 'package:PiliPlus/common/widgets/list_sheet.dart'; +import 'package:PiliPlus/common/widgets/segment_progress_bar.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/bangumi/info.dart'; +import 'package:PiliPlus/models/common/reply_type.dart'; +import 'package:PiliPlus/pages/bangumi/introduction/widgets/intro_detail.dart' + as bangumi; +import 'package:PiliPlus/pages/video/detail/introduction/widgets/intro_detail.dart' + as video; +import 'package:PiliPlus/pages/video/detail/introduction/widgets/page.dart'; +import 'package:PiliPlus/pages/video/detail/introduction/widgets/season.dart'; +import 'package:PiliPlus/pages/video/detail/member/horizontal_member_page.dart'; +import 'package:PiliPlus/pages/video/detail/reply_reply/view.dart'; +import 'package:PiliPlus/pages/video/detail/widgets/ai_detail.dart'; +import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/utils/global_data.dart'; +import 'package:PiliPlus/utils/id_utils.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:auto_orientation/auto_orientation.dart'; +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; +import 'package:floating/floating.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:get/get.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/models/common/search_type.dart'; +import 'package:PiliPlus/pages/bangumi/introduction/index.dart'; +import 'package:PiliPlus/pages/danmaku/view.dart'; +import 'package:PiliPlus/pages/video/detail/reply/index.dart'; +import 'package:PiliPlus/pages/video/detail/controller.dart'; +import 'package:PiliPlus/pages/video/detail/introduction/index.dart'; +import 'package:PiliPlus/pages/video/detail/related/index.dart'; +import 'package:PiliPlus/plugin/pl_player/index.dart'; +import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart'; +import 'package:PiliPlus/services/service_locator.dart'; +import 'package:PiliPlus/utils/storage.dart'; +import 'package:PiliPlus/models/bangumi/info.dart' as bangumi; +import 'package:PiliPlus/models/video_detail_res.dart' as video; +import 'package:screen_brightness/screen_brightness.dart'; + +import '../../../services/shutdown_timer_service.dart'; +import 'widgets/header_control.dart'; +import 'package:PiliPlus/common/widgets/spring_physics.dart'; + +class VideoDetailPageV extends StatefulWidget { + const VideoDetailPageV({super.key}); + + @override + State createState() => _VideoDetailPageVState(); + static final RouteObserver routeObserver = + RouteObserver(); +} + +class _VideoDetailPageVState extends State + with TickerProviderStateMixin, RouteAware, WidgetsBindingObserver { + late VideoDetailController videoDetailController; + late VideoReplyController _videoReplyController; + PlPlayerController? plPlayerController; + late StreamController appbarStream; + late VideoIntroController videoIntroController; + late BangumiIntroController bangumiIntroController; + late final _introController = ScrollController(); + late String heroTag; + + double doubleOffset = 0; + + // 自动退出全屏 + late bool autoExitFullscreen; + late bool autoPlayEnable; + late bool horizontalScreen; + late bool enableVerticalExpand; + late bool autoPiP; + late bool pipNoDanmaku; + late bool removeSafeArea; + // late bool showStatusBarBackgroundColor; + // final Floating floating = Floating(); + // 生命周期监听 + // late final AppLifecycleListener _lifecycleListener; + bool isShowing = true; + // StreamSubscription? _bufferedListener; + bool get isFullScreen => plPlayerController?.isFullScreen.value ?? false; + + bool get _shouldShowSeasonPanel => + (videoIntroController.videoDetail.value.ugcSeason != null || + ((videoIntroController.videoDetail.value.pages?.length ?? 0) > 1)) && + context.orientation == Orientation.landscape && + videoDetailController.horizontalSeasonPanel; + + bool get _horizontalPreview => + context.orientation == Orientation.landscape && + videoDetailController.horizontalPreview; + + StreamSubscription? _listenerDetail; + StreamSubscription? _listenerLoadingState; + StreamSubscription? _listenerCid; + + Box get setting => GStorage.setting; + + final GlobalKey relatedVideoPanelKey = GlobalKey(); + final GlobalKey videoPlayerKey = GlobalKey(); + final GlobalKey videoReplyPanelKey = GlobalKey(); + + @override + void initState() { + super.initState(); + + PlPlayerController.setPlayCallBack(playCallBack); + if (Get.arguments != null && Get.arguments['heroTag'] != null) { + heroTag = Get.arguments['heroTag']; + } + videoDetailController = Get.put(VideoDetailController(), tag: heroTag); + + if (videoDetailController.showReply) { + _videoReplyController = Get.put( + VideoReplyController(aid: videoDetailController.oid.value), + tag: heroTag, + ); + } + videoIntroController = Get.put(VideoIntroController(), tag: heroTag); + _listenerDetail = videoIntroController.videoDetail.listen((value) { + if (!context.mounted) return; + videoPlayerServiceHandler.onVideoDetailChange( + value, videoDetailController.cid.value); + }); + if (videoDetailController.videoType == SearchType.media_bangumi) { + bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); + _listenerLoadingState = + bangumiIntroController.loadingState.listen((value) { + if (!context.mounted) return; + if (value is Success) { + videoPlayerServiceHandler.onVideoDetailChange( + value.response, + videoDetailController.cid.value, + ); + } + }); + _listenerCid = videoDetailController.cid.listen((p0) { + if (!context.mounted) return; + if (bangumiIntroController.loadingState.value is Success) { + videoPlayerServiceHandler.onVideoDetailChange( + (bangumiIntroController.loadingState.value as Success).response, + p0, + ); + } + }); + } + autoExitFullscreen = + setting.get(SettingBoxKey.enableAutoExit, defaultValue: true); + horizontalScreen = + setting.get(SettingBoxKey.horizontalScreen, defaultValue: false); + autoPlayEnable = + setting.get(SettingBoxKey.autoPlayEnable, defaultValue: false); + autoPiP = setting.get(SettingBoxKey.autoPiP, defaultValue: false); + pipNoDanmaku = setting.get(SettingBoxKey.pipNoDanmaku, defaultValue: false); + enableVerticalExpand = + setting.get(SettingBoxKey.enableVerticalExpand, defaultValue: false); + removeSafeArea = setting.get(SettingBoxKey.videoPlayerRemoveSafeArea, + defaultValue: false); + // showStatusBarBackgroundColor = setting.get( + // SettingBoxKey.videoPlayerShowStatusBarBackgroundColor, + // defaultValue: false); + if (removeSafeArea) hideStatusBar(); + videoSourceInit(); + appbarStreamListen(); + // lifecycleListener(); + autoScreen(); + if (Platform.isAndroid) { + Utils.channel.setMethodCallHandler((call) async { + if (call.method == 'onUserLeaveHint') { + if (autoPiP && + plPlayerController?.playerStatus.status.value == + PlayerStatus.playing) { + enterPip(); + } + } + }); + } + + if (videoDetailController.showReply) { + WidgetsBinding.instance.addPostFrameCallback((_) { + videoDetailController.scrollKey.currentState?.innerController + .addListener(innerScrollListener); + }); + } + + WidgetsBinding.instance.addObserver(this); + } + + // 获取视频资源,初始化播放器 + Future videoSourceInit() async { + videoDetailController.queryVideoUrl(); + if (videoDetailController.showReply) { + _videoReplyController.queryData(); + } + if (videoDetailController.autoPlay.value) { + plPlayerController = videoDetailController.plPlayerController; + plPlayerController!.addStatusLister(playerListener); + plPlayerController!.addPositionListener(positionListener); + await plPlayerController!.autoEnterFullscreen(); + } + } + + void positionListener(Duration position) { + videoDetailController.playedTime = position; + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + videoIntroController.startTimer(); + videoDetailController.plPlayerController.showDanmaku = true; + } else if (state == AppLifecycleState.paused) { + videoIntroController.canelTimer(); + videoDetailController.plPlayerController.showDanmaku = false; + } + } + + void playCallBack() { + plPlayerController?.play(); + } + + // 流 + appbarStreamListen() { + appbarStream = StreamController(); + } + + // 播放器状态监听 + void playerListener(PlayerStatus? status) async { + try { + bool isPlaying = status == PlayerStatus.playing; + if (isPlaying) { + if (scrollController.offset != 0) { + isExpanding = true; + animationController.value = 0; + animationController.forward(); + } else { + videoDetailController.scrollKey.currentState?.setState(() {}); + } + } else { + videoDetailController.scrollKey.currentState?.setState(() {}); + } + } catch (e) { + debugPrint('handle playe status: $e'); + } + + if (status == PlayerStatus.completed) { + try { + if ((videoDetailController.steinEdgeInfo?['edges']['questions'][0] + ['choices'] as List?) + ?.isNotEmpty == + true) { + videoDetailController.showSteinEdgeInfo.value = true; + return; + } + } catch (_) {} + shutdownTimerService.handleWaitingFinished(); + bool notExitFlag = false; + + /// 顺序播放 列表循环 + if (videoDetailController.isPlayAll) { + notExitFlag = videoIntroController.nextPlay(); + } else if (plPlayerController!.playRepeat != PlayRepeat.pause && + plPlayerController!.playRepeat != PlayRepeat.singleCycle) { + if (videoDetailController.videoType == SearchType.video) { + notExitFlag = videoIntroController.nextPlay(); + } else if (videoDetailController.videoType == + SearchType.media_bangumi) { + notExitFlag = bangumiIntroController.nextPlay(); + } + } + + /// 单个循环 + if (plPlayerController!.playRepeat == PlayRepeat.singleCycle) { + notExitFlag = true; + plPlayerController!.play(repeat: true); + } + + // 结束播放退出全屏 + if (!notExitFlag && autoExitFullscreen) { + plPlayerController!.triggerFullScreen(status: false); + if (plPlayerController!.doubleSpeedStatus.value) { + plPlayerController!.setDoubleSpeedStatus(false); + } + if (plPlayerController!.controlsLock.value) { + plPlayerController!.onLockControl(false); + } + } + // 播放完展示控制栏 + if (videoDetailController.floating != null && !notExitFlag) { + PiPStatus currentStatus = + await videoDetailController.floating!.pipStatus; + if (currentStatus == PiPStatus.disabled) { + plPlayerController!.onLockControl(false); + } + } + } + } + + // 继续播放或重新播放 + void continuePlay() async { + plPlayerController!.play(); + } + + /// 未开启自动播放时触发播放 + Future handlePlay() async { + if (videoDetailController.isQuerying) { + debugPrint('handlePlay: querying'); + return; + } + if (videoDetailController.videoUrl == null || + videoDetailController.audioUrl == null) { + debugPrint('handlePlay: videoUrl/audioUrl not initialized'); + videoDetailController.queryVideoUrl(); + return; + } + plPlayerController = videoDetailController.plPlayerController; + videoDetailController.isShowCover.value = false; + videoDetailController.autoPlay.value = true; + if (videoDetailController.preInitPlayer) { + await plPlayerController!.play(); + } else { + await videoDetailController.playerInit(autoplay: true); + } + plPlayerController!.addStatusLister(playerListener); + plPlayerController!.addPositionListener(positionListener); + await plPlayerController!.autoEnterFullscreen(); + } + + // // 生命周期监听 + // void lifecycleListener() { + // _lifecycleListener = AppLifecycleListener( + // onResume: () => _handleTransition('resume'), + // // 后台 + // onInactive: () => _handleTransition('inactive'), + // // 在Android和iOS端不生效 + // onHide: () => _handleTransition('hide'), + // onShow: () => _handleTransition('show'), + // onPause: () => _handleTransition('pause'), + // onRestart: () => _handleTransition('restart'), + // onDetach: () => _handleTransition('detach'), + // // 只作用于桌面端 + // onExitRequested: () { + // ScaffoldMessenger.maybeOf(context) + // ?.showSnackBar(const SnackBar(content: Text("拦截应用退出"))); + // return Future.value(AppExitResponse.cancel); + // }, + // ); + // } + + @override + void dispose() { + _listenerDetail?.cancel(); + _listenerLoadingState?.cancel(); + _listenerCid?.cancel(); + + videoDetailController.skipTimer?.cancel(); + videoDetailController.skipTimer = null; + + try { + animationController.removeListener(animListener); + animationController.dispose(); + if (videoDetailController.showReply) { + videoDetailController.scrollKey.currentState?.innerController + .removeListener(innerScrollListener); + } + scrollController.removeListener(scrollListener); + scrollController.dispose(); + } catch (_) {} + + WidgetsBinding.instance.removeObserver(this); + if (!Get.previousRoute.startsWith('/video')) { + ScreenBrightness().resetApplicationScreenBrightness(); + PlPlayerController.setPlayCallBack(null); + } + videoDetailController.positionSubscription?.cancel(); + videoIntroController.canelTimer(); + appbarStream.close(); + // floating.dispose(); + // videoDetailController.floating?.dispose(); + videoIntroController.videoDetail.close(); + videoDetailController.cid.close(); + if (!horizontalScreen) { + AutoOrientation.portraitUpMode(); + } + shutdownTimerService.handleWaitingFinished(); + // _bufferedListener?.cancel(); + if (plPlayerController != null) { + videoDetailController.makeHeartBeat(); + plPlayerController!.removeStatusLister(playerListener); + plPlayerController!.removePositionListener(positionListener); + plPlayerController!.dispose(); + } else { + PlPlayerController.updatePlayCount(); + } + videoPlayerServiceHandler.onVideoDetailDispose(); + VideoDetailPageV.routeObserver.unsubscribe(this); + // _lifecycleListener.dispose(); + showStatusBar(); + // _animationController.dispose(); + super.dispose(); + } + + @override + // 离开当前页面时 + void didPushNext() async { + // _bufferedListener?.cancel(); + if (videoDetailController.imageStatus) { + return; + } + + WidgetsBinding.instance.removeObserver(this); + + ScreenBrightness().resetApplicationScreenBrightness(); + + videoDetailController.positionSubscription?.cancel(); + videoIntroController.canelTimer(); + + videoDetailController.playerStatus = + plPlayerController?.playerStatus.status.value; + + /// 开启 + // if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: true) + // as bool) { + videoDetailController.brightness = plPlayerController?.brightness.value; + // } + if (plPlayerController != null) { + videoDetailController.makeHeartBeat(); + videoDetailController.vttSubtitlesIndex = + plPlayerController!.vttSubtitlesIndex.value; + videoDetailController.showVP = plPlayerController!.showVP.value; + plPlayerController!.removeStatusLister(playerListener); + plPlayerController!.removePositionListener(positionListener); + plPlayerController!.pause(); + } + isShowing = false; + super.didPushNext(); + } + + @override + // 返回当前页面时 + void didPopNext() async { + if (videoDetailController.imageStatus) { + return; + } + + WidgetsBinding.instance.addObserver(this); + + if (videoDetailController.plPlayerController.playerStatus.status.value == + PlayerStatus.playing && + videoDetailController.playerStatus != PlayerStatus.playing) { + videoDetailController.plPlayerController.pause(); + } + + isShowing = true; + PlPlayerController.setPlayCallBack(playCallBack); + videoIntroController.startTimer(); + if (mounted) { + if (videoDetailController.brightness != null) { + plPlayerController + ?.setCurrBrightness(videoDetailController.brightness!); + if (videoDetailController.brightness != -1.0) { + ScreenBrightness().setApplicationScreenBrightness( + videoDetailController.brightness!); + } else { + ScreenBrightness().resetApplicationScreenBrightness(); + } + } else { + ScreenBrightness().resetApplicationScreenBrightness(); + } + } + super.didPopNext(); + // final bool autoplay = autoPlayEnable; + videoDetailController.autoPlay.value = + !videoDetailController.isShowCover.value; + if (videoDetailController.isShowCover.value.not) { + await videoDetailController.playerInit( + autoplay: videoDetailController.playerStatus == PlayerStatus.playing, + ); + } else if (videoDetailController.preInitPlayer && + videoDetailController.isQuerying.not && + videoDetailController.videoState.value is! Error) { + await videoDetailController.playerInit(); + } + + // if (videoDetailController.playerStatus == PlayerStatus.playing) { + // plPlayerController?.play(); + // } + + /// 未开启自动播放时,未播放跳转下一页返回/播放后跳转下一页返回 + // if (autoplay) { + // // await Future.delayed(const Duration(milliseconds: 300)); + // debugPrint(plPlayerController); + // if (plPlayerController?.buffered.value == Duration.zero) { + // _bufferedListener = plPlayerController?.buffered.listen((p0) { + // debugPrint("p0"); + // debugPrint(p0); + // if (p0 > Duration.zero) { + // _bufferedListener!.cancel(); + // plPlayerController?.seekTo(videoDetailController.defaultST); + // plPlayerController?.play(); + // } + // }); + // } else { + // plPlayerController?.seekTo(videoDetailController.defaultST); + // plPlayerController?.play(); + // } + // } + Future.delayed(const Duration(milliseconds: 600), () { + AutoOrientation.fullAutoMode(); + }); + plPlayerController?.addStatusLister(playerListener); + plPlayerController?.addPositionListener(positionListener); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + VideoDetailPageV.routeObserver + .subscribe(this, ModalRoute.of(context)! as PageRoute); + } + + // void _handleTransition(String name) { + // switch (name) { + // case 'inactive': + // if (plPlayerController != null && + // playerStatus == PlayerStatus.playing) { + // autoEnterPip(); + // } + // break; + // } + // } + + void enterPip() { + if (Get.currentRoute.startsWith('/video')) { + videoDetailController.floating?.enable( + EnableManual( + aspectRatio: Rational( + videoDetailController.data.dash!.video!.first.width!, + videoDetailController.data.dash!.video!.first.height!, + ), + ), + ); + } + } + + late final AnimationController animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + )..addListener(animListener); + late final ScrollController scrollController = ScrollController() + ..addListener(scrollListener); + late final double defVideoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + late double videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; + late RxDouble scrollRatio = 0.0.obs; + late bool isExpanding = false; + + void animListener() { + if (animationController.isForwardOrCompleted) { + videoDetailController.scrollKey.currentState?.setState(() {}); + } + } + + void scrollListener() { + if (scrollController.hasClients) { + if (scrollController.offset == 0) { + scrollRatio.value = 0; + } else { + double offset = + scrollController.offset - (videoHeight - defVideoHeight); + if (offset > 0) { + scrollRatio.value = clampDouble( + offset.toPrecision(2) / + (defVideoHeight - kToolbarHeight).toPrecision(2), + 0.0, + 1.0, + ); + } else { + scrollRatio.value = 0; + } + } + } + } + + void innerScrollListener() { + final ScrollDirection? direction = videoDetailController + .scrollKey + .currentState + ?.innerController + .positions + .firstOrNull + ?.userScrollDirection; + if (direction == ScrollDirection.forward) { + if (mounted) { + _videoReplyController.showFab(); + } + } else if (direction == ScrollDirection.reverse) { + if (mounted) { + _videoReplyController.hideFab(); + } + } + } + + Widget get childWhenDisabled => SafeArea( + top: !removeSafeArea && + MediaQuery.of(context).orientation == Orientation.portrait && + isFullScreen, + bottom: !removeSafeArea && + MediaQuery.of(context).orientation == Orientation.portrait && + isFullScreen, + left: false, //!isFullScreen, + right: false, //!isFullScreen, + child: Scaffold( + resizeToAvoidBottomInset: false, + key: videoDetailController.scaffoldKey, + // backgroundColor: Colors.black, + appBar: removeSafeArea + ? null + : PreferredSize( + preferredSize: Size.fromHeight(0), + child: Obx( + () => Stack( + children: [ + AppBar( + backgroundColor: Colors.black, + toolbarHeight: 0, + ), + if (scrollRatio.value != 0 && + scrollController.offset != 0) + AppBar( + backgroundColor: Theme.of(context) + .colorScheme + .surface + .withOpacity(scrollRatio.value), + toolbarHeight: 0, + ), + ], + ), + ), + ), + body: Obx( + () { + if (videoDetailController.direction.value == 'vertical') { + videoHeight = max(context.height * 0.7, context.width); + } else { + videoHeight = context.width * 9 / 16; + } + return ExtendedNestedScrollView( + key: videoDetailController.scrollKey, + physics: const NeverScrollableScrollPhysics( + parent: ClampingScrollPhysics(), + ), + controller: scrollController, + onlyOneScrollInBody: true, + pinnedHeaderSliverHeightBuilder: () { + double height = isFullScreen + ? MediaQuery.sizeOf(context).height + : isExpanding + ? clampDouble(videoHeight * animationController.value, + kToolbarHeight, videoHeight) + + 45 + : plPlayerController?.playerStatus.status.value == + PlayerStatus.playing + ? defVideoHeight + 45 + : kToolbarHeight; + if (isExpanding && animationController.value == 1) { + isExpanding = false; + WidgetsBinding.instance.addPostFrameCallback((_) { + videoDetailController.scrollKey.currentState + ?.setState(() {}); + }); + } + return height; + }, + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverAppBar( + automaticallyImplyLeading: false, + pinned: true, + expandedHeight: isFullScreen + ? MediaQuery.sizeOf(context).height + : videoHeight, + flexibleSpace: Stack( + children: [ + Obx( + () { + final double videoWidth = context.width; + if (MediaQuery.of(context).orientation == + Orientation.landscape && + !horizontalScreen && + !isFullScreen && + isShowing && + mounted) { + hideStatusBar(); + } + if (MediaQuery.of(context).orientation == + Orientation.portrait && + !isFullScreen && + isShowing && + mounted) { + if (videoDetailController.imageStatus.not && + removeSafeArea.not) { + showStatusBar(); + } + } + return Container( + color: Colors.black, + // showStatusBarBackgroundColor ? null : Colors.black, + height: MediaQuery.of(context).orientation == + Orientation.landscape || + isFullScreen + ? MediaQuery.sizeOf(context).height - + (MediaQuery.of(context).orientation == + Orientation.landscape || + removeSafeArea + ? 0 + : MediaQuery.of(context) + .padding + .top) + : videoHeight, + width: context.width, + child: PopScope( + canPop: !isFullScreen && + (horizontalScreen || + MediaQuery.of(context).orientation == + Orientation.portrait), + onPopInvokedWithResult: + _onPopInvokedWithResult, + child: videoPlayer(videoWidth, videoHeight), + ), + ); + }, + ), + Obx( + () { + Widget toolbar() => Opacity( + opacity: scrollRatio.value, + child: Container( + color: + Theme.of(context).colorScheme.surface, + alignment: Alignment.topCenter, + child: SizedBox( + height: kToolbarHeight, + child: Stack( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回', + icon: Icon( + FontAwesomeIcons + .arrowLeft, + size: 15, + color: Theme.of(context) + .colorScheme + .onSurface, + ), + onPressed: Get.back, + ), + ), + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回主页', + icon: Icon( + FontAwesomeIcons.house, + size: 15, + color: Theme.of(context) + .colorScheme + .onSurface, + ), + onPressed: () { + Get.until((route) => + route.isFirst); + }, + ), + ), + ], + ), + ), + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.play_arrow_rounded, + color: Theme.of(context) + .colorScheme + .primary, + ), + Text( + '${plPlayerController == null ? '立即' : plPlayerController!.playerStatus.status.value == PlayerStatus.completed ? '重新' : '继续'}播放', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + return scrollRatio.value == 0 || + scrollController.offset == 0 + ? const SizedBox.shrink() + : Positioned.fill( + bottom: -2, + child: GestureDetector( + onTap: () async { + scrollRatio.value = 0; + if (plPlayerController == null) { + handlePlay(); + } else { + if (plPlayerController! + .videoPlayerController! + .state + .completed) { + await plPlayerController! + .videoPlayerController! + .seek(Duration.zero); + plPlayerController! + .videoPlayerController! + .play(); + } else { + plPlayerController! + .videoPlayerController! + .playOrPause(); + } + } + }, + behavior: HitTestBehavior.opaque, + child: toolbar(), + ), + ); + }, + ), + ], + ), + ), + ]; + }, + body: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + buildTabbar( + showReply: videoDetailController.showReply, + onTap: () { + videoDetailController + .scrollKey.currentState?.outerController + .animToTop(); + }, + ), + Expanded( + child: videoTabBarView( + controller: videoDetailController.tabCtr, + children: [ + videoIntro(true, false), + if (videoDetailController.showReply) + videoReplyPanel(false), + if (_shouldShowSeasonPanel) seasonPanel, + ], + ), + ), + ], + ), + ), + ); + }, + ), + ), + ); + + Widget get childWhenDisabledAlmostSquareInner => Obx( + () { + if (enableVerticalExpand && + plPlayerController?.direction.value == 'vertical') { + final double videoHeight = context.height - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)); + final double videoWidth = videoHeight * 9 / 16; + return Row( + children: [ + SizedBox( + height: videoHeight, + width: isFullScreen ? context.width : videoWidth, + child: PopScope( + canPop: !isFullScreen, + onPopInvokedWithResult: _onPopInvokedWithResult, + child: videoPlayer(videoWidth, videoHeight), + ), + ), + Expanded( + child: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + buildTabbar(showReply: videoDetailController.showReply), + Expanded( + child: videoTabBarView( + controller: videoDetailController.tabCtr, + children: [ + videoIntro(), + if (videoDetailController.showReply) + videoReplyPanel(), + if (_shouldShowSeasonPanel) seasonPanel, + ], + ), + ), + ], + ), + ), + ), + ], + ); + } + final double videoHeight = context.height / 2.5; + final double videoWidth = context.width; + return Column( + children: [ + SizedBox( + width: videoWidth, + height: isFullScreen + ? context.height - + (removeSafeArea + ? 0 + : (MediaQuery.of(context).padding.top + + MediaQuery.of(context).padding.bottom)) + : videoHeight, + child: PopScope( + canPop: !isFullScreen, + onPopInvokedWithResult: _onPopInvokedWithResult, + child: videoPlayer(videoWidth, videoHeight), + ), + ), + Expanded( + child: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + buildTabbar( + needIndicator: false, + showReply: videoDetailController.showReply, + ), + Expanded( + child: Row( + children: [ + Expanded(child: videoIntro()), + if (videoDetailController.showReply) + Expanded(child: videoReplyPanel()), + if (_shouldShowSeasonPanel) + Expanded(child: seasonPanel), + ], + ), + ) + ], + ), + ), + ), + ], + ); + }, + ); + + Widget get childWhenDisabledLandscapeInner => Obx( + () { + if (enableVerticalExpand && + plPlayerController?.direction.value == 'vertical') { + final double videoHeight = context.height - + (removeSafeArea ? 0 : MediaQuery.of(context).padding.top); + final double videoWidth = videoHeight * 9 / 16; + return Row( + children: [ + Expanded( + child: + isFullScreen ? const SizedBox.shrink() : videoIntro()), + SizedBox( + height: videoHeight, + width: isFullScreen ? context.width : videoWidth, + child: PopScope( + canPop: !isFullScreen, + onPopInvokedWithResult: _onPopInvokedWithResult, + child: videoPlayer(videoWidth, videoHeight), + ), + ), + Expanded( + child: Expanded( + child: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + buildTabbar( + showIntro: false, + showReply: videoDetailController.showReply, + ), + Expanded( + child: videoTabBarView( + controller: videoDetailController.tabCtr, + children: [ + if (videoDetailController.showReply) + videoReplyPanel(), + if (_shouldShowSeasonPanel) seasonPanel, + ], + ), + ), + ], + ), + ), + ), + ), + // Expanded( + // child: TabBarView( + // physics: const ClampingScrollPhysics(), + // 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( + // bvid: videoDetailController.bvid, + // oid: videoDetailController.oid.value, + // ), + // ) + // ], + // ), + // ), + ], + ); + } + double videoWidth = + max(context.height / context.width * 1.04, 1 / 2) * context.width; + if (context.width >= 560) { + videoWidth = min(videoWidth, context.width - 280); + } + final double videoHeight = videoWidth * 9 / 16; + return Row( + children: [ + Column( + children: [ + SizedBox( + width: isFullScreen ? context.width : videoWidth, + height: isFullScreen ? context.height : videoHeight, + child: PopScope( + canPop: !isFullScreen, + onPopInvokedWithResult: _onPopInvokedWithResult, + child: videoPlayer(videoWidth, videoHeight), + ), + ), + Offstage( + offstage: isFullScreen, + child: SizedBox( + width: videoWidth, + height: context.height - + videoHeight - + (removeSafeArea + ? 0 + : MediaQuery.of(context).padding.top), + child: videoIntro(false), + ), + ), + ], + ), + Offstage( + offstage: isFullScreen, + 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: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + body: Column( + children: [ + buildTabbar( + introText: '相关视频', + showIntro: videoDetailController.videoType == + SearchType.video && + videoDetailController.showRelatedVideo, + showReply: videoDetailController.showReply, + ), + Expanded( + child: videoTabBarView( + controller: videoDetailController.tabCtr, + children: [ + if (videoDetailController.videoType == + SearchType.video && + videoDetailController.showRelatedVideo) + Material( + color: Colors.transparent, + child: CustomScrollView( + controller: _introController, + slivers: [ + RelatedVideoPanel( + key: relatedVideoPanelKey, + heroTag: heroTag), + ], + ), + ), + if (videoDetailController.showReply) + videoReplyPanel(), + if (_shouldShowSeasonPanel) seasonPanel, + ], + ), + ), + ], + ), + ), + ), + ) + ], + ); + }, + ); + + Widget get childWhenDisabledLandscape => Stack( + children: [ + Scaffold( + resizeToAvoidBottomInset: false, + key: videoDetailController.scaffoldKey, + // backgroundColor: Colors.black, + appBar: removeSafeArea + ? null + : AppBar( + backgroundColor: Colors.black, + // showStatusBarBackgroundColor ? null : Colors.black, + toolbarHeight: 0, + // systemOverlayStyle: SystemUiOverlayStyle( + // statusBarIconBrightness: + // Theme.of(context).brightness == Brightness.dark || + // !showStatusBarBackgroundColor + // ? Brightness.light + // : Brightness.dark, + // systemNavigationBarColor: Colors.transparent, + // ), + ), + body: Container( + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + left: !removeSafeArea && !isFullScreen, + right: !removeSafeArea && !isFullScreen, + top: !removeSafeArea, + bottom: false, //!removeSafeArea, + child: childWhenDisabledLandscapeInner), + ), + ), + ], + ); + + Widget get childWhenDisabledAlmostSquare => Stack(children: [ + Scaffold( + resizeToAvoidBottomInset: false, + key: videoDetailController.scaffoldKey, + // backgroundColor: Colors.black, + appBar: removeSafeArea + ? null + : AppBar( + backgroundColor: Colors.black, + // showStatusBarBackgroundColor ? null : Colors.black, + toolbarHeight: 0, + // systemOverlayStyle: SystemUiOverlayStyle( + // statusBarIconBrightness: + // Theme.of(context).brightness == Brightness.dark || + // !showStatusBarBackgroundColor + // ? Brightness.light + // : Brightness.dark, + // systemNavigationBarColor: Colors.transparent, + // ), + ), + body: Container( + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + left: !removeSafeArea && !isFullScreen, + right: !removeSafeArea && !isFullScreen, + top: !removeSafeArea, + bottom: false, //!removeSafeArea, + child: childWhenDisabledAlmostSquareInner))) + ]); + + Widget get childWhenEnabled => Obx( + () => !videoDetailController.autoPlay.value + ? const SizedBox() + : PLVideoPlayer( + key: Key(heroTag), + plPlayerController: plPlayerController!, + videoIntroController: + videoDetailController.videoType == SearchType.video + ? videoIntroController + : null, + bangumiIntroController: + videoDetailController.videoType == SearchType.media_bangumi + ? bangumiIntroController + : null, + headerControl: HeaderControl( + controller: plPlayerController!, + videoDetailCtr: videoDetailController, + heroTag: heroTag, + ), + danmuWidget: pipNoDanmaku + ? null + : Obx( + () => PlDanmaku( + key: Key(videoDetailController.danmakuCid.value + .toString()), + isPipMode: true, + cid: videoDetailController.danmakuCid.value, + playerController: plPlayerController!, + ), + ), + showEpisodes: showEpisodes, + showViewPoints: showViewPoints, + ), + ); + + Widget get manualPlayerWidget => Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value, + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + child: AppBar( + primary: false, + foregroundColor: Colors.white, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: false, + title: Row( + children: [ + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回', + icon: const Icon( + FontAwesomeIcons.arrowLeft, + size: 15, + color: Colors.white, + ), + onPressed: Get.back, + ), + ), + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回主页', + icon: const Icon( + FontAwesomeIcons.house, + size: 15, + color: Colors.white, + ), + onPressed: () { + Get.until((route) => route.isFirst); + }, + ), + ), + ], + ), + actions: [ + PopupMenuButton( + onSelected: (String type) async { + switch (type) { + case 'later': + var res = await UserHttp.toViewLater( + bvid: videoDetailController.bvid); + SmartDialog.showToast(res['msg']); + break; + case 'report': + if (videoDetailController.userInfo == null) { + SmartDialog.showToast('账号未登录'); + } else { + Get.toNamed('/webview', parameters: { + 'url': + 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(videoDetailController.bvid)}&bvid=${videoDetailController.bvid}' + }); + } + break; + } + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + value: 'later', + child: Text('稍后再看'), + ), + const PopupMenuItem( + value: 'report', + child: Text('举报'), + ), + ], + ), + ], + ), + ), + Positioned( + right: 12, + bottom: 10, + child: IconButton( + tooltip: '播放', + onPressed: handlePlay, + icon: Image.asset( + 'assets/images/play.png', + width: 60, + height: 60, + ), + ), + ), + ], + ), + ), + ); + + Widget get plPlayer => Obx( + key: videoPlayerKey, + () => videoDetailController.videoState.value is! Success + ? const SizedBox.shrink() + : !videoDetailController.autoPlay.value || + plPlayerController?.videoController == null + ? const SizedBox.shrink() + : PLVideoPlayer( + key: Key(heroTag), + plPlayerController: 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!, + ), + ), + showEpisodes: showEpisodes, + showViewPoints: showViewPoints, + ), + ); + + Widget autoChoose(Widget childWhenDisabled) { + if (Platform.isAndroid) { + return PiPSwitcher( + getChildWhenDisabled: () => childWhenDisabled, + getChildWhenEnabled: () => childWhenEnabled, + floating: videoDetailController.floating, + ); + } + return childWhenDisabled; + } + + @override + Widget build(BuildContext context) { + if (!horizontalScreen) { + return autoChoose(childWhenDisabled); + } + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // if (!isShowing) { + // return ColoredBox(color: Theme.of(context).colorScheme.surface); + // } + if (constraints.maxWidth > constraints.maxHeight * 1.25) { +// hideStatusBar(); +// videoDetailController.hiddenReplyReplyPanel(); + return autoChoose(childWhenDisabledLandscape); + } else if (constraints.maxWidth * (9 / 16) < + (2 / 5) * constraints.maxHeight) { + // if (!isFullScreen) { + // if (!removeSafeArea) showStatusBar(); + // } + return autoChoose(childWhenDisabled); + } else { + // if (!isFullScreen) { + // if (!removeSafeArea) 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) { + // showStatusBar(); + // } + // } + // if (Platform.isAndroid) { + // return PiPSwitcher( + // childWhenDisabled: + // !horizontalScreen || orientation == Orientation.portrait + // ? childWhenDisabled + // : childWhenDisabledLandscape, + // childWhenEnabled: childWhenEnabled, + // floating: floating, + // ); + // } + // return !horizontalScreen || orientation == Orientation.portrait + // ? childWhenDisabled + // : childWhenDisabledLandscape; + }, + ); + } + + Widget buildTabbar({ + bool needIndicator = true, + String introText = '简介', + bool showIntro = true, + bool showReply = true, + VoidCallback? onTap, + }) { + List tabs = [ + if (showIntro) introText, + if (showReply) '评论', + if (_shouldShowSeasonPanel) '播放列表', + ]; + if (videoDetailController.tabCtr.length != tabs.length) { + videoDetailController.tabCtr = TabController( + vsync: this, + length: tabs.length, + initialIndex: + videoDetailController.tabCtr.index.clamp(0, tabs.length - 1), + ); + } + + Widget tabbar() => TabBar( + labelColor: needIndicator.not || tabs.length == 1 + ? Theme.of(context).colorScheme.onSurface + : null, + indicatorColor: + needIndicator.not || tabs.length == 1 ? Colors.transparent : null, + padding: EdgeInsets.zero, + controller: videoDetailController.tabCtr, + labelStyle: const TextStyle(fontSize: 13), + labelPadding: + const EdgeInsets.symmetric(horizontal: 10.0), // 设置每个标签的宽度 + dividerColor: Colors.transparent, + onTap: (value) { + void animToTop() { + if (onTap != null) { + onTap(); + return; + } + String text = tabs[value]; + if (text == '简介' || text == '相关视频') { + _introController.animToTop(); + } else if (text.startsWith('评论')) { + _videoReplyController.animateToTop(); + } + } + + if (needIndicator.not || tabs.length == 1) { + animToTop(); + } else if (videoDetailController.tabCtr.indexIsChanging.not) { + animToTop(); + } + }, + tabs: tabs.map((text) { + if (text == '评论') { + return Tab( + text: + '评论${_videoReplyController.count.value == -1 ? '' : ' ${Utils.numFormat(_videoReplyController.count.value)}'}', + ); + } else { + return Tab(text: text); + } + }).toList(), + ); + + return Container( + width: double.infinity, + height: 45, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1, + color: Theme.of(context).dividerColor.withOpacity(0.1), + ), + ), + ), + child: Row( + children: [ + if (tabs.isEmpty) + const Spacer() + else + Flexible( + flex: tabs.length == 3 ? 2 : 1, + child: showReply ? Obx(() => tabbar()) : tabbar(), + ), + Flexible( + flex: 1, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + height: 32, + child: TextButton( + style: ButtonStyle( + padding: WidgetStateProperty.all(EdgeInsets.zero), + ), + onPressed: videoDetailController.showShootDanmakuSheet, + child: Text( + '发弹幕', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ), + SizedBox( + width: 38, + height: 38, + child: Obx( + () => IconButton( + onPressed: () { + videoDetailController + .plPlayerController.isOpenDanmu.value = + !videoDetailController + .plPlayerController.isOpenDanmu.value; + setting.put( + SettingBoxKey.enableShowDanmaku, + videoDetailController + .plPlayerController.isOpenDanmu.value); + }, + icon: SvgPicture.asset( + videoDetailController + .plPlayerController.isOpenDanmu.value + ? 'assets/images/video/danmu_open.svg' + : 'assets/images/video/danmu_close.svg', + // ignore: deprecated_member_use + color: videoDetailController + .plPlayerController.isOpenDanmu.value + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + const SizedBox(width: 14), + ], + ), + ), + ), + ], + ), + ); + } + + Widget videoPlayer(double videoWidth, double videoHeight) => Stack( + children: [ + if (isShowing) plPlayer, + + /// 关闭自动播放时 手动播放 + if (!videoDetailController.autoPlay.value) ...[ + Obx( + () => Visibility( + visible: videoDetailController.isShowCover.value, + child: Positioned( + top: 0, + left: 0, + right: 0, + child: GestureDetector( + onTap: handlePlay, + child: Obx( + () => NetworkImgLayer( + key: Key('cover'), + type: 'emote', + src: videoDetailController.videoItem['pic'], + width: videoWidth, + height: videoHeight, + ), + ), + ), + ), + ), + ), + manualPlayerWidget, + ], + + if (videoDetailController.enableSponsorBlock || + videoDetailController.continuePlayingPart) + Positioned( + left: 16, + bottom: isFullScreen ? max(75, Get.height * 0.25) : 75, + child: SizedBox( + width: MediaQuery.textScalerOf(context).scale(120), + child: AnimatedList( + padding: EdgeInsets.zero, + key: videoDetailController.listKey, + reverse: true, + shrinkWrap: true, + initialItemCount: videoDetailController.listData.length, + itemBuilder: (context, index, animation) { + return videoDetailController.buildItem( + videoDetailController.listData[index], + animation, + ); + }, + ), + ), + ), + + // for debug + // Positioned( + // right: 16, + // bottom: 75, + // child: FilledButton.tonal( + // onPressed: () { + // videoDetailController.onAddItem( + // SegmentModel( + // UUID: '', + // segmentType: SegmentType.sponsor, + // segment: Pair(first: 0, second: 0), + // skipType: SkipType.alwaysSkip, + // ), + // ); + // }, + // child: Text('skip'), + // ), + // ), + // Positioned( + // right: 16, + // bottom: 120, + // child: FilledButton.tonal( + // onPressed: () { + // videoDetailController.onAddItem(2); + // }, + // child: Text('index'), + // ), + // ), + + Obx( + () { + if (videoDetailController.showSteinEdgeInfo.value) { + try { + return Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 16, + vertical: plPlayerController?.showControls.value == true + ? 75 + : 16, + ), + child: Wrap( + spacing: 25, + runSpacing: 10, + children: (videoDetailController.steinEdgeInfo!['edges'] + ['questions'][0]['choices'] as List) + .map((item) { + return FilledButton.tonal( + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + backgroundColor: Theme.of(context) + .colorScheme + .secondaryContainer + .withOpacity(0.8), + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: () { + videoIntroController.changeSeasonOrbangu( + null, + videoDetailController.bvid, + item['cid'], + IdUtils.bv2av(videoDetailController.bvid), + null, + true, + ); + videoDetailController + .getSteinEdgeInfo(item['id']); + }, + child: Text(item['option']), + ); + }).toList(), + ), + ), + ); + } catch (e) { + debugPrint('build stein edges: $e'); + return const SizedBox.shrink(); + } + } + return const SizedBox.shrink(); + }, + ), + ], + ); + + Widget videoIntro([bool needRelated = true, bool needCtr = true]) { + Widget introPanel() => Material( + color: Colors.transparent, + child: CustomScrollView( + key: const PageStorageKey('简介'), + controller: needCtr ? _introController : null, + physics: needCtr.not + ? const NeverScrollableScrollPhysics( + parent: ClampingScrollPhysics(), + ) + : null, + slivers: [ + if (videoDetailController.videoType == SearchType.video) ...[ + VideoIntroPanel( + heroTag: heroTag, + showAiBottomSheet: showAiBottomSheet, + showIntroDetail: showIntroDetail, + showEpisodes: showEpisodes, + onShowMemberPage: onShowMemberPage, + ), + if (needRelated && videoDetailController.showRelatedVideo) ...[ + SliverToBoxAdapter( + child: Padding( + padding: + const EdgeInsets.only(top: StyleString.safeSpace), + child: Divider( + height: 1, + indent: 12, + endIndent: 12, + color: Theme.of(context) + .colorScheme + .outline + .withOpacity(0.08), + ), + ), + ), + RelatedVideoPanel( + key: relatedVideoPanelKey, heroTag: heroTag), + ] else + SliverToBoxAdapter( + child: SizedBox( + height: MediaQuery.paddingOf(context).bottom + + StyleString.safeSpace, + ), + ), + ] else if (videoDetailController.videoType == + SearchType.media_bangumi) + Obx( + () => BangumiIntroPanel( + heroTag: heroTag, + cid: videoDetailController.cid.value, + showEpisodes: showEpisodes, + showIntroDetail: showIntroDetail, + ), + ), + SliverToBoxAdapter( + child: SizedBox( + height: MediaQuery.paddingOf(context).bottom + + (videoDetailController.isPlayAll && + MediaQuery.orientationOf(context) == + Orientation.landscape + ? 75 + : 0), + ), + ) + ], + ), + ); + if (videoDetailController.isPlayAll) { + return Stack( + children: [ + introPanel(), + Positioned( + left: 12, + right: 12, + bottom: MediaQuery.of(context).padding.bottom + 12, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => videoDetailController.showMediaListPanel(context), + borderRadius: const BorderRadius.all(Radius.circular(14)), + child: Container( + height: 54, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .secondaryContainer + .withOpacity(0.95), + borderRadius: const BorderRadius.all(Radius.circular(14)), + ), + child: Row( + children: [ + const Icon(Icons.playlist_play, size: 24), + const SizedBox(width: 10), + Text( + videoDetailController.watchLaterTitle, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + fontWeight: FontWeight.bold, + letterSpacing: 0.2, + ), + ), + const Spacer(), + const Icon(Icons.keyboard_arrow_up_rounded, size: 26), + ], + ), + ), + ), + ), + ), + ], + ); + } else { + return introPanel(); + } + } + + Widget get seasonPanel => Column( + children: [ + if ((videoIntroController.videoDetail.value.pages?.length ?? 0) > 1) + if (videoIntroController.videoDetail.value.ugcSeason != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: PagesPanel( + heroTag: heroTag, + videoIntroController: videoIntroController, + bvid: videoIntroController.bvid, + changeFuc: videoIntroController.changeSeasonOrbangu, + showEpisodes: showEpisodes, + ), + ) + else + Expanded( + child: Obx( + () => ListSheetContent( + episodes: videoIntroController.videoDetail.value.pages, + bvid: videoDetailController.bvid, + aid: IdUtils.bv2av(videoDetailController.bvid), + currentCid: videoDetailController.cid.value, + isReversed: + videoIntroController.videoDetail.value.isPageReversed, + changeFucCall: videoDetailController.videoType == + SearchType.media_bangumi + ? bangumiIntroController.changeSeasonOrbangu + : videoIntroController.changeSeasonOrbangu, + showTitle: false, + isSupportReverse: videoDetailController.videoType != + SearchType.media_bangumi, + onReverse: () { + onReversePlay( + bvid: videoDetailController.bvid, + aid: IdUtils.bv2av(videoDetailController.bvid), + isSeason: false, + ); + }, + ), + ), + ), + if (videoIntroController.videoDetail.value.ugcSeason != null) ...[ + if ((videoIntroController.videoDetail.value.pages?.length ?? 0) > + 1) ...[ + const SizedBox(height: 8), + Divider( + height: 1, + color: Theme.of(context).colorScheme.outline.withOpacity(0.1), + ), + ], + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SeasonPanel( + heroTag: heroTag, + onTap: false, + ugcSeason: videoIntroController.videoDetail.value.ugcSeason!, + changeFuc: videoIntroController.changeSeasonOrbangu, + showEpisodes: showEpisodes, + pages: videoIntroController.videoDetail.value.pages, + videoIntroController: videoIntroController, + ), + ), + Expanded( + child: Obx( + () => ListSheetContent( + index: videoDetailController.seasonIndex.value, + season: videoIntroController.videoDetail.value.ugcSeason, + bvid: videoDetailController.bvid, + aid: IdUtils.bv2av(videoDetailController.bvid), + currentCid: videoDetailController.seasonCid ?? 0, + isReversed: + videoIntroController.videoDetail.value.isSeasonReversed, + changeFucCall: videoDetailController.videoType == + SearchType.media_bangumi + ? bangumiIntroController.changeSeasonOrbangu + : videoIntroController.changeSeasonOrbangu, + showTitle: false, + isSupportReverse: videoDetailController.videoType != + SearchType.media_bangumi, + onReverse: () { + onReversePlay( + bvid: videoDetailController.bvid, + aid: IdUtils.bv2av(videoDetailController.bvid), + isSeason: true, + ); + }, + ), + ), + ), + ], + ], + ); + + Widget videoReplyPanel([bool needCtr = true]) => Obx( + () => VideoReplyPanel( + key: videoReplyPanelKey, + needController: needCtr, + bvid: videoDetailController.bvid, + oid: videoDetailController.oid.value, + heroTag: heroTag, + replyReply: replyReply, + onViewImage: videoDetailController.onViewImage, + onDismissed: videoDetailController.onDismissed, + callback: _horizontalPreview + ? (imgList, index) { + final ctr = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + )..forward(); + Utils.onHorizontalPreview( + videoDetailController.childKey, + AnimationController( + vsync: this, + duration: Duration.zero, + ), + ctr, + imgList, + index, + (value) async { + if (value == false) { + await ctr.reverse(); + } + try { + ctr.dispose(); + } catch (_) {} + if (value == false) { + Get.back(); + } + }, + ); + } + : null, + ), + ); + + // 展示二级回复 + void replyReply(replyItem, id, isTop) { + EasyThrottle.throttle('replyReply', const Duration(milliseconds: 500), () { + int oid = replyItem.oid.toInt(); + int rpid = GlobalData().grpcReply ? replyItem.id.toInt() : replyItem.rpid; + videoDetailController.childKey.currentState?.showBottomSheet( + backgroundColor: Colors.transparent, + (context) => VideoReplyReplyPanel( + id: id, + oid: oid, + rpid: rpid, + firstFloor: replyItem, + replyType: ReplyType.video, + source: 'videoDetail', + isTop: isTop ?? false, + onViewImage: videoDetailController.onViewImage, + onDismissed: videoDetailController.onDismissed, + ), + ); + }); + } + + // ai总结 + showAiBottomSheet() { + videoDetailController.childKey.currentState?.showBottomSheet( + enableDrag: true, + backgroundColor: Theme.of(context).colorScheme.surface, + (context) => AiDetail(modelResult: videoIntroController.modelResult), + ); + } + + showIntroDetail(videoDetail, videoTags) { + videoDetailController.childKey.currentState?.showBottomSheet( + enableDrag: true, + backgroundColor: Theme.of(context).colorScheme.surface, + (context) => videoDetail is BangumiInfoModel + ? bangumi.IntroDetail( + bangumiDetail: videoDetail, + videoTags: videoTags, + ) + : video.IntroDetail( + videoDetail: videoDetail, + videoTags: videoTags, + ), + ); + } + + showEpisodes(index, season, episodes, bvid, aid, cid) { + Widget listSheetContent() => ListSheetContent( + index: index, + season: season, + bvid: bvid, + aid: aid, + currentCid: cid, + episodes: episodes, + isReversed: + videoDetailController.videoType == SearchType.media_bangumi + ? null + : season != null + ? videoIntroController.videoDetail.value.isSeasonReversed + : videoIntroController.videoDetail.value.isPageReversed, + isSupportReverse: + videoDetailController.videoType != SearchType.media_bangumi, + changeFucCall: + videoDetailController.videoType == SearchType.media_bangumi + ? bangumiIntroController.changeSeasonOrbangu + : videoIntroController.changeSeasonOrbangu, + onClose: Get.back, + onReverse: () { + Get.back(); + onReversePlay( + bvid: bvid, + aid: aid, + isSeason: season != null, + ); + }, + ); + if (isFullScreen) { + Utils.showFSSheet( + child: Material( + color: Theme.of(context).colorScheme.surface, + child: listSheetContent(), + ), + isFullScreen: isFullScreen, + ); + } else { + videoDetailController.childKey.currentState?.showBottomSheet( + backgroundColor: Theme.of(context).colorScheme.surface, + (context) => listSheetContent(), + ); + } + } + + void onReversePlay({ + required dynamic bvid, + required dynamic aid, + required bool isSeason, + }) { + void changeEpisode(episode) { + videoIntroController.changeSeasonOrbangu( + episode is bangumi.EpisodeItem ? episode.epId : null, + episode.runtimeType.toString() == "EpisodeItem" ? episode.bvid : bvid, + episode.cid, + episode.runtimeType.toString() == "EpisodeItem" ? episode.aid : aid, + episode is video.EpisodeItem + ? episode.arc?.pic + : episode is bangumi.EpisodeItem + ? episode.cover + : null, + ); + } + + if (isSeason) { + // reverse season + videoIntroController.videoDetail.value.isSeasonReversed = + !videoIntroController.videoDetail.value.isSeasonReversed; + videoIntroController.videoDetail.value.ugcSeason! + .sections![videoDetailController.seasonIndex.value].episodes = + videoIntroController + .videoDetail + .value + .ugcSeason! + .sections![videoDetailController.seasonIndex.value] + .episodes! + .reversed + .toList(); + + if (videoDetailController.reverseFromFirst.not) { + // keep current episode + videoDetailController.seasonIndex.refresh(); + videoDetailController.cid.refresh(); + } else { + // switch to first episode + dynamic episode = videoIntroController.videoDetail.value.ugcSeason! + .sections![videoDetailController.seasonIndex.value].episodes!.first; + if (episode.cid != videoDetailController.cid.value) { + changeEpisode(episode); + } else { + videoDetailController.seasonIndex.refresh(); + videoDetailController.cid.refresh(); + } + } + } else { + // reverse part + videoIntroController.videoDetail.value.isPageReversed = + !videoIntroController.videoDetail.value.isPageReversed; + videoIntroController.videoDetail.value.pages = + videoIntroController.videoDetail.value.pages!.reversed.toList(); + if (videoDetailController.reverseFromFirst.not) { + // keep current episode + videoDetailController.cid.refresh(); + } else { + // switch to first episode + dynamic episode = videoIntroController.videoDetail.value.pages!.first; + if (episode.cid != videoDetailController.cid.value) { + changeEpisode(episode); + } else { + videoDetailController.cid.refresh(); + } + } + } + } + + void showViewPoints() { + Widget listSheetContent() { + int currentIndex = -1; + return StatefulBuilder( + builder: (context, setState) => Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + automaticallyImplyLeading: false, + titleSpacing: 16, + title: const Text('分段信息'), + actions: [ + Text( + '分段进度条', + style: TextStyle(fontSize: 16), + ), + Obx( + () => Transform.scale( + alignment: Alignment.centerLeft, + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith((states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: + videoDetailController.plPlayerController.showVP.value, + onChanged: (value) { + videoDetailController.plPlayerController.showVP.value = + value; + }, + ), + ), + ), + iconButton( + context: context, + size: 30, + icon: Icons.clear, + tooltip: '关闭', + onPressed: Get.back, + ), + const SizedBox(width: 16), + ], + ), + body: SingleChildScrollView( + child: Column( + children: [ + ...List.generate( + videoDetailController.viewPointList.length * 2 - 1, + (rawIndex) { + if (rawIndex % 2 == 1) { + return Divider( + height: 1, + color: Theme.of(context).dividerColor.withOpacity(0.1), + ); + } + int index = rawIndex ~/ 2; + Segment segment = videoDetailController.viewPointList[index]; + if (currentIndex == -1 && + segment.from != null && + segment.to != null) { + if (videoDetailController + .plPlayerController.positionSeconds.value >= + segment.from! && + videoDetailController + .plPlayerController.positionSeconds.value < + segment.to!) { + currentIndex = index; + } + } + return ListTile( + dense: true, + onTap: segment.from != null + ? () { + currentIndex = index; + plPlayerController?.danmakuController?.clear(); + plPlayerController?.videoPlayerController + ?.seek(Duration(seconds: segment.from!)); + Get.back(); + } + : null, + leading: segment.url?.isNotEmpty == true + ? Container( + margin: const EdgeInsets.symmetric(vertical: 6), + decoration: currentIndex == index + ? BoxDecoration( + borderRadius: BorderRadius.circular(6), + border: Border.all( + width: 1.8, + strokeAlign: + BorderSide.strokeAlignOutside, + color: + Theme.of(context).colorScheme.primary, + ), + ) + : null, + child: LayoutBuilder( + builder: (context, constraints) => + NetworkImgLayer( + radius: 6, + src: segment.url, + width: constraints.maxHeight * + StyleString.aspectRatio, + height: constraints.maxHeight, + ), + ), + ) + : null, + title: Text( + segment.title ?? '', + style: TextStyle( + fontSize: 14, + fontWeight: + currentIndex == index ? FontWeight.bold : null, + color: currentIndex == index + ? Theme.of(context).colorScheme.primary + : null, + ), + ), + subtitle: Text( + '${segment.from != null ? Utils.timeFormat(segment.from) : ''} - ${segment.to != null ? Utils.timeFormat(segment.to) : ''}', + style: TextStyle( + fontSize: 13, + color: currentIndex == index + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + ), + ), + ); + }), + SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom), + ], + ), + ), + ), + ); + } + + if (isFullScreen) { + Utils.showFSSheet(child: listSheetContent(), isFullScreen: isFullScreen); + } else { + videoDetailController.childKey.currentState?.showBottomSheet( + backgroundColor: Colors.transparent, + (context) => listSheetContent(), + ); + } + } + + void _onPopInvokedWithResult(didPop, result) { + if (plPlayerController?.controlsLock.value == true) { + plPlayerController?.onLockControl(false); + return; + } + if (isFullScreen) { + plPlayerController!.triggerFullScreen(status: false); + } + if (MediaQuery.of(context).orientation == Orientation.landscape && + !horizontalScreen) { + verticalScreenForTwoSeconds(); + } + } + + void onShowMemberPage(mid) { + videoDetailController.childKey.currentState?.showBottomSheet( + backgroundColor: Theme.of(context).colorScheme.surface, + (context) { + return HorizontalMemberPage( + mid: mid, + videoDetailController: videoDetailController, + videoIntroController: videoIntroController, + ); + }, + enableDrag: true, + ); + } +} diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 52ebd3532..4d733600a 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -4,7 +4,6 @@ import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactivevie import 'package:PiliPlus/utils/extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -182,8 +181,8 @@ class ChatItem extends StatelessWidget { var bvid = content["bvid"]; final int cid = await SearchHttp.ab2c(bvid: bvid); SmartDialog.dismiss().then( - (e) => Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + (e) => Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': content['thumb'], 'heroTag': Utils.makeHeroTag(bvid), @@ -230,8 +229,8 @@ class ChatItem extends StatelessWidget { var bvid = content["bvid"]; final int cid = await SearchHttp.ab2c(bvid: bvid); SmartDialog.dismiss().then( - (_) => Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + (_) => Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': content['thumb'], 'heroTag': Utils.makeHeroTag(bvid), @@ -317,8 +316,7 @@ class ChatItem extends StatelessWidget { SmartDialog.showLoading(); final int cid = await SearchHttp.ab2c(bvid: bvid); SmartDialog.dismiss().then( - (e) => Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + (e) => Utils.toViewPage('bvid=$bvid&cid=$cid', arguments: { 'pic': i['cover_url'], 'heroTag': Utils.makeHeroTag(bvid), diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 4822f9b1f..6b94c20b7 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/pages/setting/search_page.dart'; import 'package:PiliPlus/pages/setting/sponsor_block_page.dart'; import 'package:PiliPlus/pages/setting/view.dart'; import 'package:PiliPlus/pages/video/detail/introduction/widgets/create_fav_page.dart'; +import 'package:PiliPlus/pages/video/detail/view_v.dart'; import 'package:PiliPlus/pages/webview/webview_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -63,6 +64,7 @@ class Routes { CustomGetPage(name: '/hot', page: () => const HotPage()), // 视频详情 CustomGetPage(name: '/video', page: () => const VideoDetailPage()), + CustomGetPage(name: '/videoV', page: () => const VideoDetailPageV()), // CustomGetPage(name: '/webview', page: () => const WebviewPageNew()), // 设置 diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index a00b83fb8..fae32d25c 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -134,8 +134,8 @@ class PiliScheme { if (aid != null || bvid != null) { if (queryParameters['cid'] != null) { bvid ??= IdUtils.av2bv(int.parse(aid!)); - Utils.toDupNamed( - '/video?bvid=$bvid&cid=${queryParameters['cid']}', + Utils.toViewPage( + 'bvid=$bvid&cid=${queryParameters['cid']}', arguments: { 'pic': null, 'heroTag': Utils.makeHeroTag(aid), @@ -143,6 +143,7 @@ class PiliScheme { 'progress': int.tryParse(queryParameters['dm_progress']!), }, off: off, + preventDuplicates: false, ); } else { videoPush( @@ -566,14 +567,15 @@ class PiliScheme { if (showDialog) { SmartDialog.dismiss(); } - Utils.toDupNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': null, 'heroTag': Utils.makeHeroTag(aid), if (progress != null) 'progress': int.tryParse(progress), }, off: off, + preventDuplicates: false, ); } catch (e) { SmartDialog.dismiss(); diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 92c0a46d9..3c9ac224c 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -388,6 +388,9 @@ class GStorage { static bool get expandDynLivePanel => GStorage.setting .get(SettingBoxKey.expandDynLivePanel, defaultValue: false); + static bool collapsibleVideoPage = GStorage.setting + .get(SettingBoxKey.collapsibleVideoPage, defaultValue: true); + static List get dynamicDetailRatio => List.from(setting .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); @@ -640,6 +643,7 @@ class SettingBoxKey { antiGoodsReply = 'antiGoodsReply', expandDynLivePanel = 'expandDynLivePanel', springDescription = 'springDescription', + collapsibleVideoPage = 'collapsibleVideoPage', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock', diff --git a/lib/utils/url_utils.dart b/lib/utils/url_utils.dart index c792d99f3..451d2ef79 100644 --- a/lib/utils/url_utils.dart +++ b/lib/utils/url_utils.dart @@ -1,7 +1,6 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; import '../http/init.dart'; import '../http/search.dart'; @@ -66,8 +65,8 @@ class UrlUtils { String? bvid = matchRes['BV']; bvid ??= IdUtils.av2bv(aid!); final int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid); - await Get.toNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': '', 'heroTag': Utils.makeHeroTag(bvid), diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 47c479921..711166ede 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -48,6 +48,33 @@ class Utils { static const channel = MethodChannel("PiliPlus"); + static void toViewPage( + String page, { + dynamic arguments, + int? id, + bool preventDuplicates = true, + Map? parameters, + bool off = false, + }) { + if (off) { + Get.offNamed( + '${GStorage.collapsibleVideoPage ? '/videoV' : '/video'}?$page', + arguments: arguments, + id: id, + preventDuplicates: preventDuplicates, + parameters: parameters, + ); + } else { + Get.toNamed( + '${GStorage.collapsibleVideoPage ? '/videoV' : '/video'}?$page', + arguments: arguments, + id: id, + preventDuplicates: preventDuplicates, + parameters: parameters, + ); + } + } + static Future insertCreatedDyn(result) async { try { dynamic id = result['data']['dyn_id']; @@ -384,12 +411,13 @@ class Utils { String bvid = item.modules.moduleDynamic.major.archive.bvid; String cover = item.modules.moduleDynamic.major.archive.cover; int cid = await SearchHttp.ab2c(bvid: bvid); - Utils.toDupNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': cover, 'heroTag': Utils.makeHeroTag(bvid), }, + preventDuplicates: false, ); } catch (err) { SmartDialog.showToast(err.toString()); @@ -454,12 +482,13 @@ class Utils { String bvid = IdUtils.av2bv(aid); String cover = ugcSeason.cover!; int cid = await SearchHttp.ab2c(bvid: bvid); - Utils.toDupNamed( - '/video?bvid=$bvid&cid=$cid', + Utils.toViewPage( + 'bvid=$bvid&cid=$cid', arguments: { 'pic': cover, 'heroTag': Utils.makeHeroTag(bvid), }, + preventDuplicates: false, ); break; @@ -845,14 +874,15 @@ class Utils { for (EpisodeItem item in item.episodes!) { if (item.epId.toString() == epId.toString()) { // view as normal video - Utils.toDupNamed( - '/video?bvid=${item.bvid}&cid=${item.cid}&seasonId=${data.seasonId}&epId=${item.epId}', + Utils.toViewPage( + 'bvid=${item.bvid}&cid=${item.cid}&seasonId=${data.seasonId}&epId=${item.epId}', arguments: { 'pgcApi': true, 'pic': item.cover, 'heroTag': Utils.makeHeroTag(item.cid), 'videoType': SearchType.video, }, + preventDuplicates: false, ); return; } @@ -873,14 +903,15 @@ class Utils { ) ?? data.episodes!.first : data.episodes!.first; - Utils.toDupNamed( - '/video?bvid=${episode.bvid}&cid=${episode.cid}&seasonId=${data.seasonId}&epId=${episode.epId}&type=${data.type}', + Utils.toViewPage( + 'bvid=${episode.bvid}&cid=${episode.cid}&seasonId=${data.seasonId}&epId=${episode.epId}&type=${data.type}', arguments: { 'pic': episode.cover, 'heroTag': Utils.makeHeroTag(episode.cid), 'videoType': SearchType.media_bangumi, 'bangumiItem': data, }, + preventDuplicates: false, ); } else { SmartDialog.showToast(result['msg']);