feat: medialist: reverse play #70

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-31 18:16:08 +08:00
parent df41729d74
commit 098e2220cc
8 changed files with 105 additions and 59 deletions

View File

@@ -28,3 +28,22 @@ Widget iconButton({
), ),
); );
} }
Widget mediumButton({
String? tooltip,
IconData? icon,
VoidCallback? onPressed,
}) {
return SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: tooltip,
icon: Icon(icon),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: onPressed,
),
);
}

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/icon_button.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/video.dart'; import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi; import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi;
@@ -291,7 +292,7 @@ class _ListSheetContentState extends State<ListSheetContent>
StreamBuilder( StreamBuilder(
stream: _favStream?.stream, stream: _favStream?.stream,
builder: (context, snapshot) => snapshot.hasData builder: (context, snapshot) => snapshot.hasData
? _mediumButton( ? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅', tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1 icon: _seasonFav == 1
? Icons.notifications_off_outlined ? Icons.notifications_off_outlined
@@ -313,7 +314,7 @@ class _ListSheetContentState extends State<ListSheetContent>
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
), ),
_mediumButton( mediumButton(
tooltip: '跳至顶部', tooltip: '跳至顶部',
icon: Icons.vertical_align_top, icon: Icons.vertical_align_top,
onPressed: () { onPressed: () {
@@ -331,7 +332,7 @@ class _ListSheetContentState extends State<ListSheetContent>
} catch (_) {} } catch (_) {}
}, },
), ),
_mediumButton( mediumButton(
tooltip: '跳至底部', tooltip: '跳至底部',
icon: Icons.vertical_align_bottom, icon: Icons.vertical_align_bottom,
onPressed: () { onPressed: () {
@@ -349,7 +350,7 @@ class _ListSheetContentState extends State<ListSheetContent>
} catch (_) {} } catch (_) {}
}, },
), ),
_mediumButton( mediumButton(
tooltip: '跳至当前', tooltip: '跳至当前',
icon: Icons.my_location, icon: Icons.my_location,
onPressed: () async { onPressed: () async {
@@ -382,7 +383,7 @@ class _ListSheetContentState extends State<ListSheetContent>
StreamBuilder( StreamBuilder(
stream: _indexStream?.stream, stream: _indexStream?.stream,
initialData: _index, initialData: _index,
builder: (context, snapshot) => _mediumButton( builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '顺序' : '倒序', tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
icon: !reverse[snapshot.data] icon: !reverse[snapshot.data]
? MdiIcons.sortNumericAscending ? MdiIcons.sortNumericAscending
@@ -395,7 +396,7 @@ class _ListSheetContentState extends State<ListSheetContent>
), ),
), ),
if (widget.onClose != null) if (widget.onClose != null)
_mediumButton( mediumButton(
tooltip: '关闭', tooltip: '关闭',
icon: Icons.close, icon: Icons.close,
onPressed: widget.onClose, onPressed: widget.onClose,
@@ -437,7 +438,7 @@ class _ListSheetContentState extends State<ListSheetContent>
); );
} }
Widget get _reverseButton => _mediumButton( Widget get _reverseButton => mediumButton(
tooltip: widget.isReversed == true ? '正序播放' : '倒序播放', tooltip: widget.isReversed == true ? '正序播放' : '倒序播放',
icon: widget.isReversed == true icon: widget.isReversed == true
? MdiIcons.sortDescending ? MdiIcons.sortDescending
@@ -461,25 +462,6 @@ class _ListSheetContentState extends State<ListSheetContent>
}, },
); );
Widget _mediumButton({
String? tooltip,
IconData? icon,
VoidCallback? onPressed,
}) {
return SizedBox(
width: 34,
height: 34,
child: IconButton(
tooltip: tooltip,
icon: Icon(icon),
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: onPressed,
),
);
}
Widget _buildBody(i, episodes) => Material( Widget _buildBody(i, episodes) => Material(
child: ScrollablePositionedList.separated( child: ScrollablePositionedList.separated(
padding: EdgeInsets.only( padding: EdgeInsets.only(

View File

@@ -31,7 +31,6 @@ class MediaVideoItemModel {
this.forbidFav, this.forbidFav,
this.moreType, this.moreType,
this.businessOid, this.businessOid,
this.isReversed = false,
}); });
int? id; int? id;
@@ -65,7 +64,6 @@ class MediaVideoItemModel {
bool? forbidFav; bool? forbidFav;
int? moreType; int? moreType;
int? businessOid; int? businessOid;
bool isReversed;
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) => factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
MediaVideoItemModel( MediaVideoItemModel(
@@ -103,7 +101,6 @@ class MediaVideoItemModel {
forbidFav: json["forbid_fav"], forbidFav: json["forbid_fav"],
moreType: json["more_type"], moreType: json["more_type"],
businessOid: json["business_oid"], businessOid: json["business_oid"],
isReversed: false,
); );
} }

View File

@@ -147,6 +147,7 @@ class FavDetailController extends MultiSelectController {
'oid': element.id, 'oid': element.id,
'favTitle': item.value.title, 'favTitle': item.value.title,
'count': item.value.mediaCount, 'count': item.value.mediaCount,
'desc': true,
}, },
); );
break; break;

View File

@@ -187,6 +187,7 @@ class LaterController extends MultiSelectController {
'count': list.length, 'count': list.length,
'favTitle': '稍后再看', 'favTitle': '稍后再看',
'mediaId': GStorage.userInfo.get('userInfoCache')?.mid, 'mediaId': GStorage.userInfo.get('userInfoCache')?.mid,
'desc': false,
}, },
); );
break; break;

View File

@@ -6,6 +6,7 @@ import 'package:PiliPalaX/models/space_archive/item.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart'; import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart' import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType; show ContributeType;
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -103,6 +104,13 @@ class MemberVideoCtr extends CommonController {
SmartDialog.showToast('已跳过不支持播放的视频'); SmartDialog.showToast('已跳过不支持播放的视频');
} }
final String heroTag = Utils.makeHeroTag(element.bvid); final String heroTag = Utils.makeHeroTag(element.bvid);
bool desc = seasonId != null ? false : true;
desc = (seasonId != null || seriesId != null) &&
(type == ContributeType.video
? order.value == 'click'
: sort.value == 'asc')
? desc.not
: desc;
Get.toNamed( Get.toNamed(
'/video?bvid=${element.bvid}&cid=${element.firstCid}', '/video?bvid=${element.bvid}&cid=${element.firstCid}',
arguments: { arguments: {
@@ -118,9 +126,11 @@ class MemberVideoCtr extends CommonController {
'mediaType': RegExp(r'page_type=([\d]+)') 'mediaType': RegExp(r'page_type=([\d]+)')
.firstMatch('${episodicButton?.uri}') .firstMatch('${episodicButton?.uri}')
?.group(1), ?.group(1),
'reverse': type == ContributeType.video 'desc': desc,
? order.value == 'click' 'sortField':
: sort.value == 'asc', type == ContributeType.video && order.value == 'click'
? 2
: 1,
}, },
); );
break; break;

View File

@@ -244,6 +244,7 @@ class VideoDetailController extends GetxController
// 页面来源 稍后再看 收藏夹 // 页面来源 稍后再看 收藏夹
String sourceType = 'normal'; String sourceType = 'normal';
late bool _mediaDesc = false;
late RxList<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[].obs; late RxList<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[].obs;
late String watchLaterTitle = ''; late String watchLaterTitle = '';
bool get isPlayAll => ['watchLater', 'fav', 'archive'].contains(sourceType); bool get isPlayAll => ['watchLater', 'fav', 'archive'].contains(sourceType);
@@ -279,6 +280,7 @@ class VideoDetailController extends GetxController
if (sourceType != 'normal') { if (sourceType != 'normal') {
watchLaterTitle = Get.arguments['favTitle']; watchLaterTitle = Get.arguments['favTitle'];
_mediaDesc = Get.arguments['desc'];
getMediaList(); getMediaList();
} }
@@ -327,31 +329,44 @@ class VideoDetailController extends GetxController
} }
} }
void getMediaList() async { void getMediaList([bool isReverse = false]) async {
if (Get.arguments['count'] != null && if (isReverse.not &&
Get.arguments['count'] != null &&
mediaList.length >= Get.arguments['count']) { mediaList.length >= Get.arguments['count']) {
return; return;
} }
bool desc =
_mediaType == 2 || Get.arguments['mediaType'] == '8' ? false : true;
var res = await UserHttp.getMediaList( var res = await UserHttp.getMediaList(
type: Get.arguments['mediaType'] ?? _mediaType, type: Get.arguments['mediaType'] ?? _mediaType,
bizId: Get.arguments['mediaId'] ?? -1, bizId: Get.arguments['mediaId'] ?? -1,
ps: 20, ps: 20,
oid: mediaList.isEmpty ? null : mediaList.last.id, oid: isReverse || mediaList.isEmpty ? null : mediaList.last.id,
otype: mediaList.isEmpty ? null : mediaList.last.type, otype: isReverse || mediaList.isEmpty ? null : mediaList.last.type,
desc: desc: _mediaDesc,
Get.arguments['mediaType'] != null && Get.arguments['reverse'] == true sortField: Get.arguments['sortField'] ?? 1,
? desc.not
: desc,
sortField:
Get.arguments['mediaType'] == null && Get.arguments['reverse'] == true
? 2
: 1,
); );
if (res['status']) { if (res['status']) {
if (res['data'].isNotEmpty) { if (res['data'].isNotEmpty) {
mediaList.addAll(res['data']); if (isReverse) {
mediaList.value = res['data'];
try {
for (MediaVideoItemModel item in mediaList) {
if (item.cid == null) {
continue;
} else {
Get.find<VideoIntroController>(tag: heroTag)
.changeSeasonOrbangu(
null,
mediaList.first.bvid,
mediaList.first.cid,
mediaList.first.aid,
mediaList.first.cover,
);
}
}
} catch (_) {}
} else {
mediaList.addAll(res['data']);
}
} }
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
@@ -366,9 +381,14 @@ class VideoDetailController extends GetxController
mediaList: mediaList, mediaList: mediaList,
changeMediaList: changeMediaList, changeMediaList: changeMediaList,
panelTitle: watchLaterTitle, panelTitle: watchLaterTitle,
bvid: bvid, getBvId: () => bvid,
count: Get.arguments['count'], count: Get.arguments['count'],
loadMoreMedia: getMediaList, loadMoreMedia: getMediaList,
desc: _mediaDesc,
onReverse: () {
_mediaDesc = !_mediaDesc;
getMediaList(true);
},
), ),
); );
} }

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/icon_button.dart';
import 'package:PiliPalaX/common/widgets/stat/danmu.dart'; import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
import 'package:PiliPalaX/common/widgets/stat/view.dart'; import 'package:PiliPalaX/common/widgets/stat/view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -9,6 +10,7 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/search.dart'; import 'package:PiliPalaX/http/search.dart';
import 'package:PiliPalaX/models/video/later.dart'; import 'package:PiliPalaX/models/video/later.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPalaX/utils/utils.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class MediaListPanel extends StatefulWidget { class MediaListPanel extends StatefulWidget {
@@ -17,17 +19,21 @@ class MediaListPanel extends StatefulWidget {
required this.mediaList, required this.mediaList,
this.changeMediaList, this.changeMediaList,
this.panelTitle, this.panelTitle,
this.bvid, required this.getBvId,
required this.loadMoreMedia, required this.loadMoreMedia,
required this.count, required this.count,
required this.desc,
required this.onReverse,
}); });
final List<MediaVideoItemModel> mediaList; final List<MediaVideoItemModel> mediaList;
final Function? changeMediaList; final Function? changeMediaList;
final String? panelTitle; final String? panelTitle;
final String? bvid; final Function getBvId;
final VoidCallback loadMoreMedia; final VoidCallback loadMoreMedia;
final int? count; final int? count;
final bool? desc;
final VoidCallback onReverse;
@override @override
State<MediaListPanel> createState() => _MediaListPanelState(); State<MediaListPanel> createState() => _MediaListPanelState();
@@ -41,7 +47,7 @@ class _MediaListPanelState extends State<MediaListPanel> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
int index = int index =
widget.mediaList.indexWhere((item) => item.bvid == widget.bvid); widget.mediaList.indexWhere((item) => item.bvid == widget.getBvId());
if (index != -1 && index != 0) { if (index != -1 && index != 0) {
try { try {
_scrollController.jumpTo(index: index); _scrollController.jumpTo(index: index);
@@ -64,8 +70,17 @@ class _MediaListPanelState extends State<MediaListPanel> {
titleSpacing: 16, titleSpacing: 16,
title: Text(widget.panelTitle ?? '稍后再看'), title: Text(widget.panelTitle ?? '稍后再看'),
actions: [ actions: [
IconButton( if (widget.desc != null)
icon: const Icon(Icons.close, size: 20), mediumButton(
tooltip: widget.desc == true ? '顺序播放' : '倒序播放',
icon: widget.desc == true
? MdiIcons.sortAscending
: MdiIcons.sortDescending,
onPressed: widget.onReverse,
),
mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: Get.back, onPressed: Get.back,
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
@@ -160,14 +175,15 @@ class _MediaListPanelState extends State<MediaListPanel> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontWeight: fontWeight:
item.bvid == widget.bvid item.bvid == widget.getBvId()
? FontWeight.bold ? FontWeight.bold
: null, : null,
color: item.bvid == widget.bvid color:
? Theme.of(context) item.bvid == widget.getBvId()
.colorScheme ? Theme.of(context)
.primary .colorScheme
: null, .primary
: null,
), ),
), ),
const Spacer(), const Spacer(),