diff --git a/lib/common/widgets/list_sheet.dart b/lib/common/widgets/list_sheet.dart index bdec5569..ad58a5ee 100644 --- a/lib/common/widgets/list_sheet.dart +++ b/lib/common/widgets/list_sheet.dart @@ -27,6 +27,7 @@ class ListSheetContent extends StatefulWidget { this.onClose, this.onReverse, this.showTitle, + this.isSupportReverse, }); final dynamic index; @@ -39,6 +40,7 @@ class ListSheetContent extends StatefulWidget { final VoidCallback? onClose; final VoidCallback? onReverse; final bool? showTitle; + final bool? isSupportReverse; @override State createState() => _ListSheetContentState(); @@ -361,7 +363,7 @@ class _ListSheetContentState extends State } catch (_) {} }, ), - if (widget.season != null) + if (widget.isSupportReverse == true) if (!_isList) _reverseButton else diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index 20dc8704..63bd4f3e 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -402,7 +402,7 @@ class _ExtraSettingState extends State { defaultVal: true, ), SetSwitchItem( - title: '倒序播放从首集开始播放', + title: '分P/合集:倒序播放从首集开始播放', subTitle: '开启则自动切换为倒序首集,否则保持当前集', leading: const Icon(Icons.u_turn_right), setKey: SettingBoxKey.reverseFromFirst, diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 59f273bb..681975a3 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -131,8 +131,13 @@ class VideoIntroController extends GetxController var result = await VideoHttp.videoIntro(bvid: bvid); if (result['status']) { if (videoDetail.value.ugcSeason?.id == result['data']?.ugcSeason?.id) { + // keep reversed season result['data']?.ugcSeason = videoDetail.value.ugcSeason; } + if (videoDetail.value.cid == result['data']?.cid) { + // keep reversed pages + result['data']?.pages = videoDetail.value.pages; + } videoDetail.value = result['data']; videoItem!['staff'] = result['data'].staff; try { diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 35462fc2..32fa6362 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -71,7 +71,7 @@ class _VideoIntroPanelState extends State () => videoIntroController.videoDetail.value.title == null ? VideoInfo( loadingStatus: true, - videoDetail: videoIntroController.videoDetail.value, + videoIntroController: videoIntroController, heroTag: widget.heroTag, showAiBottomSheet: widget.showAiBottomSheet, showIntroDetail: () => widget.showIntroDetail( @@ -83,7 +83,7 @@ class _VideoIntroPanelState extends State ) : VideoInfo( loadingStatus: false, - videoDetail: videoIntroController.videoDetail.value, + videoIntroController: videoIntroController, heroTag: widget.heroTag, showAiBottomSheet: widget.showAiBottomSheet, showIntroDetail: () => widget.showIntroDetail( @@ -99,17 +99,17 @@ class _VideoIntroPanelState extends State class VideoInfo extends StatefulWidget { final bool loadingStatus; - final VideoDetailData? videoDetail; final String heroTag; final Function showAiBottomSheet; final Function showIntroDetail; final Function showEpisodes; final ValueChanged onShowMemberPage; + final VideoIntroController videoIntroController; const VideoInfo({ super.key, this.loadingStatus = false, - this.videoDetail, + required this.videoIntroController, required this.heroTag, required this.showAiBottomSheet, required this.showIntroDetail, @@ -122,10 +122,13 @@ class VideoInfo extends StatefulWidget { } class _VideoInfoState extends State with TickerProviderStateMixin { - late final VideoIntroController videoIntroController; late final VideoDetailController videoDetailCtr; late final Map videoItem; + VideoIntroController get videoIntroController => widget.videoIntroController; + VideoDetailData get videoDetail => + widget.videoIntroController.videoDetail.value; + late final _coinKey = GlobalKey(); late final _favKey = GlobalKey(); @@ -145,7 +148,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { @override void initState() { super.initState(); - videoIntroController = Get.put(VideoIntroController(), tag: widget.heroTag); videoDetailCtr = Get.find(tag: widget.heroTag); videoItem = videoIntroController.videoItem!; @@ -232,7 +234,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onPushMember() { feedBack(); int? mid = !widget.loadingStatus - ? widget.videoDetail?.owner?.mid + ? videoDetail.owner?.mid : videoItem['owner']?.mid; if (mid != null) { if (context.orientation == Orientation.landscape && @@ -241,7 +243,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { } else { // memberHeroTag = Utils.makeHeroTag(mid); // String face = !loadingStatus - // ? widget.videoDetail!.owner!.face + // ? videoDetail.owner!.face // : videoItem['owner'].face; Get.toNamed( '/member?mid=$mid', @@ -287,7 +289,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { type: 'avatar', src: widget.loadingStatus ? videoItem['owner']?.face ?? "" - : widget.videoDetail!.owner!.face, + : videoDetail.owner!.face, width: 30, height: 30, fadeInDuration: Duration.zero, @@ -302,7 +304,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Text( widget.loadingStatus ? videoItem['owner']?.name ?? "" - : widget.videoDetail!.owner!.name, + : videoDetail.owner!.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -335,7 +337,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { childBuilder: (index) => GestureDetector( onTap: () { int? ownerMid = !widget.loadingStatus - ? widget.videoDetail?.owner?.mid + ? videoDetail.owner?.mid : videoItem['owner']?.mid; if (videoItem['staff'][index].mid == ownerMid && context.orientation == @@ -454,10 +456,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onLongPress: () { feedBack(); Utils.copyText( - '${widget.videoDetail?.title ?? videoItem['title'] ?? ''}'); + '${videoDetail.title ?? videoItem['title'] ?? ''}'); }, child: Text( - '${widget.videoDetail?.title ?? videoItem['title'] ?? ''}', + '${videoDetail.title ?? videoItem['title'] ?? ''}', maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 16), @@ -467,10 +469,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onLongPress: () { feedBack(); Utils.copyText( - '${widget.videoDetail?.title ?? videoItem['title'] ?? ''}'); + '${videoDetail.title ?? videoItem['title'] ?? ''}'); }, child: Text( - widget.videoDetail?.title ?? videoItem['title'] ?? '', + videoDetail.title ?? videoItem['title'] ?? '', style: const TextStyle(fontSize: 16), ), ), @@ -495,7 +497,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { context: context, theme: 'gray', view: !widget.loadingStatus - ? widget.videoDetail?.stat?.view ?? '-' + ? videoDetail.stat?.view ?? '-' : videoItem['stat']?.view ?? '-', size: 'medium', ), @@ -504,7 +506,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { context: context, theme: 'gray', danmu: !widget.loadingStatus - ? widget.videoDetail?.stat?.danmu ?? '-' + ? videoDetail.stat?.danmu ?? '-' : videoItem['stat']?.danmu ?? '-', size: 'medium', ), @@ -512,7 +514,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Text( Utils.dateFormat( !widget.loadingStatus - ? widget.videoDetail?.pubdate + ? videoDetail.pubdate : videoItem['pubdate'], formatType: 'detail'), style: TextStyle( @@ -670,34 +672,34 @@ class _VideoInfoState extends State with TickerProviderStateMixin { if (!isHorizontal) actionGrid(context, videoIntroController), // 合集 if (!widget.loadingStatus && - widget.videoDetail?.ugcSeason != null && + videoDetail.ugcSeason != null && (context.orientation != Orientation.landscape || (context.orientation == Orientation.landscape && videoDetailCtr.horizontalSeasonPanel.not))) Obx( () => SeasonPanel( heroTag: widget.heroTag, - ugcSeason: widget.videoDetail!.ugcSeason!, + ugcSeason: videoDetail.ugcSeason!, cid: videoIntroController.lastPlayCid.value != 0 - ? (widget.videoDetail!.pages?.isNotEmpty == true - ? widget.videoDetail!.pages!.first.cid + ? (videoDetail.pages?.isNotEmpty == true + ? videoDetail.pages!.first.cid : videoIntroController.lastPlayCid.value) - : widget.videoDetail!.pages!.first.cid, + : videoDetail.pages!.first.cid, changeFuc: videoIntroController.changeSeasonOrbangu, showEpisodes: widget.showEpisodes, - pages: widget.videoDetail!.pages, + pages: videoDetail.pages, ), ), if (!widget.loadingStatus && - widget.videoDetail?.pages != null && - widget.videoDetail!.pages!.length > 1 && + videoDetail.pages != null && + videoDetail.pages!.length > 1 && (context.orientation != Orientation.landscape || (context.orientation == Orientation.landscape && videoDetailCtr.horizontalSeasonPanel.not))) ...[ Obx( () => PagesPanel( heroTag: widget.heroTag, - pages: widget.videoDetail!.pages!, + videoDetailData: videoIntroController.videoDetail.value, cid: videoIntroController.lastPlayCid.value, bvid: videoIntroController.bvid, changeFuc: videoIntroController.changeSeasonOrbangu, @@ -760,7 +762,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { loadingStatus: widget.loadingStatus, semanticsLabel: '点赞', text: !widget.loadingStatus - ? Utils.numFormat(widget.videoDetail!.stat!.like!) + ? Utils.numFormat(videoDetail.stat!.like!) : '-', needAnim: true, hasOneThree: videoIntroController.hasLike.value && @@ -804,7 +806,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { loadingStatus: widget.loadingStatus, semanticsLabel: '投币', text: !widget.loadingStatus - ? Utils.numFormat(widget.videoDetail!.stat!.coin!) + ? Utils.numFormat(videoDetail.stat!.coin!) : '-', needAnim: true, ), @@ -820,7 +822,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { loadingStatus: widget.loadingStatus, semanticsLabel: '收藏', text: !widget.loadingStatus - ? Utils.numFormat(widget.videoDetail!.stat!.favorite!) + ? Utils.numFormat(videoDetail.stat!.favorite!) : '-', needAnim: true, ), @@ -833,7 +835,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { loadingStatus: widget.loadingStatus, semanticsLabel: '评论', text: !widget.loadingStatus - ? Utils.numFormat(widget.videoDetail!.stat!.reply!) + ? Utils.numFormat(videoDetail.stat!.reply!) : '评论'), ActionItem( icon: const Icon(FontAwesomeIcons.shareFromSquare), @@ -842,7 +844,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { loadingStatus: widget.loadingStatus, semanticsLabel: '分享', text: !widget.loadingStatus - ? Utils.numFormat(widget.videoDetail!.stat!.share!) + ? Utils.numFormat(videoDetail.stat!.share!) : '分享'), ], ), @@ -858,9 +860,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onTap: () => handleState(videoIntroController.actionLikeVideo), selectStatus: videoIntroController.hasLike.value, loadingStatus: widget.loadingStatus, - text: !widget.loadingStatus - ? widget.videoDetail!.stat!.like!.toString() - : '-', + text: + !widget.loadingStatus ? videoDetail.stat!.like!.toString() : '-', ), ), const SizedBox(width: 8), @@ -870,9 +871,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { onTap: () => handleState(videoIntroController.actionCoinVideo), selectStatus: videoIntroController.hasCoin.value, loadingStatus: widget.loadingStatus, - text: !widget.loadingStatus - ? widget.videoDetail!.stat!.coin!.toString() - : '-', + text: + !widget.loadingStatus ? videoDetail.stat!.coin!.toString() : '-', ), ), const SizedBox(width: 8), @@ -884,7 +884,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { selectStatus: videoIntroController.hasFav.value, loadingStatus: widget.loadingStatus, text: !widget.loadingStatus - ? widget.videoDetail!.stat!.favorite!.toString() + ? videoDetail.stat!.favorite!.toString() : '-', ), ), @@ -896,9 +896,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { }, selectStatus: false, loadingStatus: widget.loadingStatus, - text: !widget.loadingStatus - ? widget.videoDetail!.stat!.reply!.toString() - : '-', + text: !widget.loadingStatus ? videoDetail.stat!.reply!.toString() : '-', ), const SizedBox(width: 8), ActionRowItem( @@ -907,7 +905,7 @@ class _VideoInfoState extends State with TickerProviderStateMixin { selectStatus: false, loadingStatus: widget.loadingStatus, // text: !loadingStatus - // ? widget.videoDetail!.stat!.share!.toString() + // ? videoDetail.stat!.share!.toString() // : '-', text: '转发'), ]); diff --git a/lib/pages/video/detail/introduction/widgets/page.dart b/lib/pages/video/detail/introduction/widgets/page.dart index ac22adf6..4e78358f 100644 --- a/lib/pages/video/detail/introduction/widgets/page.dart +++ b/lib/pages/video/detail/introduction/widgets/page.dart @@ -11,19 +11,19 @@ import '../../../../../utils/id_utils.dart'; class PagesPanel extends StatefulWidget { const PagesPanel({ super.key, - required this.pages, required this.cid, required this.bvid, required this.changeFuc, required this.heroTag, required this.showEpisodes, + required this.videoDetailData, }); - final List pages; final int cid; final String bvid; final Function changeFuc; final String heroTag; final Function showEpisodes; + final VideoDetailData videoDetailData; @override State createState() => _PagesPanelState(); @@ -32,8 +32,6 @@ class PagesPanel extends StatefulWidget { class _PagesPanelState extends State { late int cid; late int pageIndex; - // final String heroTag = Get.arguments['heroTag']; - late final String heroTag; late VideoDetailController _videoDetailController; final ScrollController _scrollController = ScrollController(); StreamSubscription? _listener; @@ -42,12 +40,14 @@ class _PagesPanelState extends State { void initState() { super.initState(); cid = widget.cid; - heroTag = widget.heroTag; - _videoDetailController = Get.find(tag: heroTag); - pageIndex = widget.pages.indexWhere((Part e) => e.cid == cid); - _listener = _videoDetailController.cid.listen((int p0) { - cid = p0; - pageIndex = max(0, widget.pages.indexWhere((Part e) => e.cid == cid)); + _videoDetailController = + Get.find(tag: widget.heroTag); + pageIndex = + widget.videoDetailData.pages!.indexWhere((Part e) => e.cid == cid); + _listener = _videoDetailController.cid.listen((int cid) { + this.cid = cid; + pageIndex = max(0, + widget.videoDetailData.pages!.indexWhere((Part e) => e.cid == cid)); if (!mounted) return; const double itemWidth = 150; // 每个列表项的宽度 final double targetOffset = min((pageIndex * itemWidth) - (itemWidth / 2), @@ -80,7 +80,7 @@ class _PagesPanelState extends State { const Text('视频选集 '), Expanded( child: Text( - ' 正在播放:${widget.pages[pageIndex].pagePart}', + ' 正在播放:${widget.videoDetailData.pages![pageIndex].pagePart}', overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, @@ -98,13 +98,13 @@ class _PagesPanelState extends State { onPressed: () => widget.showEpisodes( null, null, - widget.pages, + widget.videoDetailData.pages!, widget.bvid, IdUtils.bv2av(widget.bvid), cid, ), child: Text( - '共${widget.pages.length}集', + '共${widget.videoDetailData.pages!.length}集', style: const TextStyle(fontSize: 13), ), ), @@ -118,14 +118,14 @@ class _PagesPanelState extends State { child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, - itemCount: widget.pages.length, + itemCount: widget.videoDetailData.pages!.length, itemExtent: 150, itemBuilder: (BuildContext context, int i) { bool isCurrentIndex = pageIndex == i; return Container( width: 150, margin: EdgeInsets.only( - right: i != widget.pages.length - 1 ? 10 : 0, + right: i != widget.videoDetailData.pages!.length - 1 ? 10 : 0, ), child: Material( color: Theme.of(context).colorScheme.onInverseSurface, @@ -136,7 +136,7 @@ class _PagesPanelState extends State { widget.changeFuc( null, widget.bvid, - widget.pages[i].cid, + widget.videoDetailData.pages![i].cid, IdUtils.bv2av(widget.bvid), null, ) @@ -157,7 +157,7 @@ class _PagesPanelState extends State { ], Expanded( child: Text( - widget.pages[i].pagePart!, + widget.videoDetailData.pages![i].pagePart!, maxLines: 1, style: TextStyle( fontSize: 13, diff --git a/lib/pages/video/detail/introduction/widgets/season.dart b/lib/pages/video/detail/introduction/widgets/season.dart index 24328dab..c55c5803 100644 --- a/lib/pages/video/detail/introduction/widgets/season.dart +++ b/lib/pages/video/detail/introduction/widgets/season.dart @@ -55,10 +55,15 @@ class _SeasonPanelState extends State { currentIndex.value = episodes.indexWhere( (EpisodeItem e) => e.cid == _videoDetailController.seasonCid); _listener = _videoDetailController.cid.listen((int cid) { - if (widget.pages != null && widget.pages!.length != 1) { - bool isPart = widget.pages?.indexWhere((item) => item.cid == cid) != -1; - if (isPart) return; + if (_videoDetailController.seasonCid == cid) { + //refresh + _findEpisode(); + currentIndex.value = episodes.indexWhere( + (EpisodeItem e) => e.cid == _videoDetailController.seasonCid); + return; } + bool isPart = widget.pages?.indexWhere((item) => item.cid == cid) != -1; + if (isPart) return; _videoDetailController.seasonCid = cid; _findEpisode(); currentIndex.value = episodes.indexWhere( diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index b6072dd0..a6c2fb04 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -1470,7 +1470,7 @@ class _VideoDetailPageState extends State padding: const EdgeInsets.symmetric(horizontal: 14), child: PagesPanel( heroTag: heroTag, - pages: videoIntroController.videoDetail.value.pages!, + videoDetailData: videoIntroController.videoDetail.value, cid: videoIntroController.lastPlayCid.value, bvid: videoIntroController.bvid, changeFuc: videoIntroController.changeSeasonOrbangu, @@ -1516,10 +1516,13 @@ class _VideoDetailPageState extends State ? bangumiIntroController.changeSeasonOrbangu : videoIntroController.changeSeasonOrbangu, showTitle: false, + isSupportReverse: videoDetailController.videoType != + SearchType.media_bangumi, onReverse: () { onReversePlay( - videoDetailController.bvid, - IdUtils.bv2av(videoDetailController.bvid), + bvid: videoDetailController.bvid, + aid: IdUtils.bv2av(videoDetailController.bvid), + isSeason: true, ); }, ), @@ -1592,6 +1595,8 @@ class _VideoDetailPageState extends State aid: aid, currentCid: cid, episodes: episodes, + isSupportReverse: + videoDetailController.videoType != SearchType.media_bangumi, changeFucCall: videoDetailController.videoType == SearchType.media_bangumi ? bangumiIntroController.changeSeasonOrbangu @@ -1606,7 +1611,11 @@ class _VideoDetailPageState extends State }, onReverse: () { Get.back(); - onReversePlay(bvid, aid); + onReversePlay( + bvid: bvid, + aid: aid, + isSeason: season != null, + ); }, ); if (isFullScreen) { @@ -1621,38 +1630,64 @@ class _VideoDetailPageState extends State } } - void onReversePlay(bvid, aid) { - videoIntroController.videoDetail.value.ugcSeason! - .sections![videoDetailController.seasonIndex.value].episodes = - videoIntroController - .videoDetail - .value - .ugcSeason! - .sections![videoDetailController.seasonIndex.value] - .episodes! - .reversed - .toList(); + 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 (videoDetailController.reverseFromFirst.not) { - // keep current episode - videoDetailController.seasonIndex.refresh(); - videoDetailController.cid.refresh(); + if (isSeason) { + // reverse season + 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 { - // switch to first episode - dynamic episode = videoIntroController.videoDetail.value.ugcSeason! - .sections![videoDetailController.seasonIndex.value].episodes!.first; - if (episode.cid != videoDetailController.cid.value) { - 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, - ); + // reverse part + videoIntroController.videoDetail.value.pages = + videoIntroController.videoDetail.value.pages!.reversed.toList(); + videoIntroController.lastPlayCid.refresh(); + 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); + } } } }