diff --git a/lib/common/widgets/list_sheet.dart b/lib/common/widgets/list_sheet.dart index 978eb37e..12543aac 100644 --- a/lib/common/widgets/list_sheet.dart +++ b/lib/common/widgets/list_sheet.dart @@ -17,14 +17,16 @@ import '../../utils/utils.dart'; class ListSheetContent extends StatefulWidget { const ListSheetContent({ super.key, - this.index, + this.index, // tab index this.season, - required this.episodes, + this.episodes, this.bvid, this.aid, required this.currentCid, required this.changeFucCall, this.onClose, + this.onReverse, + this.showTitle, }); final dynamic index; @@ -35,6 +37,8 @@ class ListSheetContent extends StatefulWidget { final int currentCid; final Function changeFucCall; final VoidCallback? onClose; + final VoidCallback? onReverse; + final bool? showTitle; @override State createState() => _ListSheetContentState(); @@ -47,10 +51,11 @@ class _ListSheetContentState extends State late List reverse; int get _index => widget.index ?? 0; - bool get _isList => - widget.season != null && + late final bool _isList = widget.season != null && widget.season?.sections is List && widget.season.sections.length > 1; + dynamic get episodes => + widget.episodes ?? widget.season?.sections[_index].episodes; TabController? _ctr; StreamController? _indexStream; int? _seasonFav; @@ -59,11 +64,34 @@ class _ListSheetContentState extends State @override void didUpdateWidget(ListSheetContent oldWidget) { super.didUpdateWidget(oldWidget); - currentIndex = _currentIndex; + int currentIndex = _currentIndex; + + void jumpToCurrent() { + if (this.currentIndex != currentIndex) { + this.currentIndex = currentIndex; + try { + itemScrollController[_index].jumpTo(index: currentIndex); + } catch (_) {} + } + } + + // jump to current + if (_ctr != null && widget.index != _ctr?.index) { + _ctr?.animateTo(_index); + Future.delayed(const Duration(milliseconds: 225)).then((_) { + jumpToCurrent(); + }); + } else { + jumpToCurrent(); + } } - int get _currentIndex => - max(0, widget.episodes.indexWhere((e) => e.cid == widget.currentCid)); + int get _currentIndex => max( + 0, + _isList + ? widget.season.sections[_ctr?.index].episodes + .indexWhere((e) => e.cid == widget.currentCid) + : episodes.indexWhere((e) => e.cid == widget.currentCid)); @override void initState() { @@ -150,6 +178,7 @@ class _ListSheetContentState extends State } SmartDialog.showToast('切换到:$title'); widget.onClose?.call(); + currentIndex = index; widget.changeFucCall( episode is bangumi.EpisodeItem ? episode.epId : null, episode.runtimeType.toString() == "EpisodeItem" @@ -242,13 +271,15 @@ class _ListSheetContentState extends State children: [ Container( height: 45, - padding: const EdgeInsets.only(left: 14, right: 14), + padding: EdgeInsets.symmetric( + horizontal: widget.showTitle != false ? 14 : 6), child: Row( children: [ - Text( - '合集(${_isList ? widget.season.epCount : widget.episodes?.length ?? ''})', - style: Theme.of(context).textTheme.titleMedium, - ), + if (widget.showTitle != false) + Text( + '合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})', + style: Theme.of(context).textTheme.titleMedium, + ), StreamBuilder( stream: _favStream?.stream, builder: (context, snapshot) => snapshot.hasData @@ -286,7 +317,7 @@ class _ListSheetContentState extends State ? widget.season.sections[_ctr?.index].episodes .length - 1 - : widget.episodes.length - 1, + : episodes.length - 1, duration: const Duration(milliseconds: 200), ); } catch (_) {} @@ -303,7 +334,7 @@ class _ListSheetContentState extends State ? widget.season.sections[_ctr?.index].episodes .length - 1 - : widget.episodes.length - 1 + : episodes.length - 1 : 0, duration: const Duration(milliseconds: 200), ); @@ -326,6 +357,33 @@ class _ListSheetContentState extends State } catch (_) {} }, ), + if (widget.season != null) + _mediumButton( + tooltip: '倒叙播放', + icon: Icons.u_turn_right, + onPressed: () async { + // jump to current + if (_ctr != null && _ctr?.index != (_index)) { + _ctr?.animateTo(_index); + await Future.delayed(const Duration(milliseconds: 225)); + } + try { + itemScrollController[_ctr?.index ?? 0].scrollTo( + index: currentIndex, + duration: const Duration(milliseconds: 200), + ); + } catch (_) {} + + widget.onReverse?.call(); + WidgetsBinding.instance.addPostFrameCallback((_) { + try { + itemScrollController[_ctr?.index ?? 0].jumpTo( + index: currentIndex, + ); + } catch (_) {} + }); + }, + ), const Spacer(), StreamBuilder( stream: _indexStream?.stream, @@ -378,7 +436,7 @@ class _ListSheetContentState extends State index, widget.season.sections[index].episodes), ), ) - : _buildBody(null, widget.episodes), + : _buildBody(null, episodes), ), ], ), diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index 3ebdfa50..c202e1b4 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -127,6 +127,7 @@ class _BangumiPanelState extends State { widget.pages[currentIndex].bvid, widget.pages[currentIndex].aid, cid, + null, ), child: Text( widget.newEp?['desc']?.contains('连载') == true diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 3eaeb80e..f12d5f55 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -11,7 +11,6 @@ import 'package:PiliPalaX/http/init.dart'; import 'package:PiliPalaX/http/user.dart'; import 'package:PiliPalaX/models/video/later.dart'; import 'package:PiliPalaX/models/video/play/subtitle.dart'; -import 'package:PiliPalaX/models/video_detail_res.dart'; import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart'; import 'package:PiliPalaX/pages/video/detail/related/controller.dart'; import 'package:PiliPalaX/pages/video/detail/reply/controller.dart'; @@ -225,7 +224,6 @@ class VideoDetailController extends GetxController late final horizontalSeasonPanel = GStorage.horizontalSeasonPanel; late int seasonCid = 0; late RxInt seasonIndex = 0.obs; - late RxList episodes = [].obs; late final bool enableSponsorBlock; PlayerStatus? playerStatus; diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 18b08df8..59f273bb 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -127,10 +127,13 @@ class VideoIntroController extends GetxController // 获取视频简介&分p Future queryVideoIntro() async { - await queryVideoTags(); + queryVideoTags(); var result = await VideoHttp.videoIntro(bvid: bvid); if (result['status']) { - videoDetail.value = result['data']!; + if (videoDetail.value.ugcSeason?.id == result['data']?.ugcSeason?.id) { + result['data']?.ugcSeason = videoDetail.value.ugcSeason; + } + videoDetail.value = result['data']; videoItem!['staff'] = result['data'].staff; try { final videoDetailController = diff --git a/lib/pages/video/detail/introduction/widgets/page.dart b/lib/pages/video/detail/introduction/widgets/page.dart index ac22adf6..3b3c0757 100644 --- a/lib/pages/video/detail/introduction/widgets/page.dart +++ b/lib/pages/video/detail/introduction/widgets/page.dart @@ -102,6 +102,7 @@ class _PagesPanelState extends State { widget.bvid, IdUtils.bv2av(widget.bvid), cid, + null, ), child: Text( '共${widget.pages.length}集', diff --git a/lib/pages/video/detail/introduction/widgets/season.dart b/lib/pages/video/detail/introduction/widgets/season.dart index d962317d..ed71b25c 100644 --- a/lib/pages/video/detail/introduction/widgets/season.dart +++ b/lib/pages/video/detail/introduction/widgets/season.dart @@ -32,6 +32,7 @@ class _SeasonPanelState extends State { int currentIndex = 0; late VideoDetailController _videoDetailController; StreamSubscription? _listener; + List episodes = []; @override void initState() { @@ -43,7 +44,7 @@ class _SeasonPanelState extends State { /// 根据 cid 找到对应集,找到对应 episodes /// 有多个episodes时,只显示其中一个 _findEpisode(); - if (_videoDetailController.episodes.isEmpty) { + if (episodes.isEmpty) { return; } @@ -51,14 +52,16 @@ class _SeasonPanelState extends State { // episodes = widget.ugcSeason.sections! // .firstWhere((e) => e.seasonId == widget.ugcSeason.id) // .episodes; - currentIndex = _videoDetailController.episodes.indexWhere( + currentIndex = episodes.indexWhere( (EpisodeItem e) => e.cid == _videoDetailController.seasonCid); _listener = _videoDetailController.cid.listen((int p0) { - bool isPart = widget.pages?.indexWhere((item) => item.cid == p0) != -1; - if (isPart) return; + if (widget.pages != null && widget.pages!.length != 1) { + bool isPart = widget.pages?.indexWhere((item) => item.cid == p0) != -1; + if (isPart) return; + } _videoDetailController.seasonCid = p0; _findEpisode(); - currentIndex = _videoDetailController.episodes.indexWhere( + currentIndex = episodes.indexWhere( (EpisodeItem e) => e.cid == _videoDetailController.seasonCid); if (!mounted) return; setState(() {}); @@ -84,7 +87,7 @@ class _SeasonPanelState extends State { @override Widget build(BuildContext context) { - if (_videoDetailController.episodes.isEmpty) { + if (episodes.isEmpty) { return const SizedBox(); } return Builder(builder: (BuildContext context) { @@ -105,10 +108,15 @@ class _SeasonPanelState extends State { : () => widget.showEpisodes( _videoDetailController.seasonIndex.value, widget.ugcSeason, - _videoDetailController.episodes, + null, _videoDetailController.bvid, null, _videoDetailController.seasonCid, + () { + setState(() { + currentIndex = episodes.length - 1 - currentIndex; + }); + }, ), child: Padding( padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), @@ -130,10 +138,10 @@ class _SeasonPanelState extends State { ), const SizedBox(width: 10), Text( - '${currentIndex + 1}/${_videoDetailController.episodes.length}', + '${currentIndex + 1}/${episodes.length}', style: Theme.of(context).textTheme.labelMedium, semanticsLabel: - '第${currentIndex + 1}集,共${_videoDetailController.episodes.length}集', + '第${currentIndex + 1}集,共${episodes.length}集', ), const SizedBox(width: 6), const Icon( @@ -156,8 +164,10 @@ class _SeasonPanelState extends State { final List episodesList = sections[i].episodes!; for (int j = 0; j < episodesList.length; j++) { if (episodesList[j].cid == _videoDetailController.seasonCid) { - _videoDetailController.seasonIndex.value = i; - _videoDetailController.episodes.value = episodesList; + if (_videoDetailController.seasonIndex.value != i) { + _videoDetailController.seasonIndex.value = i; + } + episodes = episodesList; break; } } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index e2a94919..659a93ea 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -1505,8 +1505,7 @@ class _VideoDetailPageState extends State child: Obx( () => ListSheetContent( index: videoDetailController.seasonIndex.value, - season: videoIntroController.videoDetail.value.ugcSeason!, - episodes: videoDetailController.episodes, + season: videoIntroController.videoDetail.value.ugcSeason, bvid: videoDetailController.bvid, aid: IdUtils.bv2av(videoDetailController.bvid), currentCid: videoDetailController.seasonCid, @@ -1514,6 +1513,8 @@ class _VideoDetailPageState extends State SearchType.media_bangumi ? bangumiIntroController.changeSeasonOrbangu : videoIntroController.changeSeasonOrbangu, + showTitle: false, + onReverse: onReversePlay, ), ), ), @@ -1576,14 +1577,14 @@ class _VideoDetailPageState extends State ); } - showEpisodes(index, season, episodes, bvid, aid, cid) { + showEpisodes(index, season, episodes, bvid, aid, cid, onReverse) { Widget listSheetContent() => ListSheetContent( index: index, season: season, - episodes: episodes, bvid: bvid, aid: aid, currentCid: cid, + episodes: episodes, changeFucCall: videoDetailController.videoType == SearchType.media_bangumi ? bangumiIntroController.changeSeasonOrbangu @@ -1596,6 +1597,11 @@ class _VideoDetailPageState extends State Get.back(); } }, + onReverse: () { + Get.back(); + onReversePlay(); + onReverse(); + }, ); if (isFullScreen) { videoDetailController.bsController = @@ -1609,6 +1615,21 @@ class _VideoDetailPageState extends State } } + void onReversePlay() { + videoIntroController.videoDetail.value.ugcSeason! + .sections![videoDetailController.seasonIndex.value].episodes = + videoIntroController + .videoDetail + .value + .ugcSeason! + .sections![videoDetailController.seasonIndex.value] + .episodes! + .reversed + .toList(); + videoDetailController.seasonIndex.refresh(); + videoDetailController.cid.refresh(); + } + void showViewPoints() { Widget listSheetContent(context, [bool isFS = false]) { int currentIndex = -1; diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index c0a0d911..2f3dcc11 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -411,6 +411,7 @@ class _PLVideoPlayerState extends State bvid, IdUtils.bv2av(bvid), currentCid, + null, ); }, ),