feat: play all member archives

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-27 12:50:11 +08:00
parent 329eb31387
commit 64672dbdf9
9 changed files with 190 additions and 129 deletions

View File

@@ -1,10 +1,7 @@
import 'dart:convert';
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/video/later.dart'; import 'package:PiliPalaX/models/video/later.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:html/parser.dart';
import '../common/constants.dart'; import '../common/constants.dart';
import '../models/model_hot_video_item.dart'; import '../models/model_hot_video_item.dart';
import '../models/user/fav_detail.dart'; import '../models/user/fav_detail.dart';
@@ -517,7 +514,9 @@ class UserHttp {
required int bizId, required int bizId,
required int ps, required int ps,
int? oid, int? oid,
int otype = 2, int? otype,
bool withCurrent = false,
bool desc = true,
}) async { }) async {
var res = await Request().get( var res = await Request().get(
Api.mediaList, Api.mediaList,
@@ -525,14 +524,14 @@ class UserHttp {
'mobi_app': 'web', 'mobi_app': 'web',
'type': type, 'type': type,
'biz_id': bizId, 'biz_id': bizId,
'oid': oid ?? '', if (oid != null) 'oid': oid,
'otype': otype, // video:2 // bangumi: 24 if (otype != null) 'otype': otype, // video:2 // bangumi: 24
'ps': ps, 'ps': ps,
'direction': false, 'direction': false,
'desc': true, 'desc': desc,
'sort_field': 1, 'sort_field': 1,
'tid': 0, 'tid': 0,
'with_current': false, 'with_current': withCurrent,
}, },
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
@@ -551,30 +550,30 @@ class UserHttp {
} }
// 解析收藏夹视频 // 解析收藏夹视频
static Future parseFavVideo({ // static Future parseFavVideo({
required int mediaId, // required int mediaId,
required int oid, // required int oid,
required String bvid, // required String bvid,
}) async { // }) async {
var res = await Request().get( // var res = await Request().get(
'https://www.bilibili.com/list/ml$mediaId', // 'https://www.bilibili.com/list/ml$mediaId',
queryParameters: { // queryParameters: {
'oid': mediaId, // 'oid': mediaId,
'bvid': bvid, // 'bvid': bvid,
}, // },
); // );
String scriptContent = // String scriptContent =
extractScriptContents(parse(res.data).body!.outerHtml)[0]; // extractScriptContents(parse(res.data).body!.outerHtml)[0];
int startIndex = scriptContent.indexOf('{'); // int startIndex = scriptContent.indexOf('{');
int endIndex = scriptContent.lastIndexOf('};'); // int endIndex = scriptContent.lastIndexOf('};');
String jsonContent = scriptContent.substring(startIndex, endIndex + 1); // String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
// 解析JSON字符串为Map // // 解析JSON字符串为Map
Map<String, dynamic> jsonData = json.decode(jsonContent); // Map<String, dynamic> jsonData = json.decode(jsonContent);
return { // return {
'status': true, // 'status': true,
'data': jsonData['resourceList'] // 'data': jsonData['resourceList']
.map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e)) // .map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
.toList() // .toList()
}; // };
} // }
} }

View File

@@ -125,7 +125,7 @@ class FavDetailController extends MultiSelectController {
); );
} }
Future toViewPlayAll() async { void toViewPlayAll() {
if (loadingState.value is Success) { if (loadingState.value is Success) {
List<FavDetailItemData> list = (loadingState.value as Success).response; List<FavDetailItemData> list = (loadingState.value as Success).response;
for (FavDetailItemData element in list) { for (FavDetailItemData element in list) {

View File

@@ -1,6 +1,7 @@
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/model_hot_video_item.dart'; import 'package:PiliPalaX/models/model_hot_video_item.dart';
import 'package:PiliPalaX/pages/common/multi_select_controller.dart'; import 'package:PiliPalaX/pages/common/multi_select_controller.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -165,7 +166,7 @@ class LaterController extends MultiSelectController {
} }
// 稍后再看播放全部 // 稍后再看播放全部
Future toViewPlayAll() async { void toViewPlayAll() {
if (loadingState.value is Success) { if (loadingState.value is Success) {
List<HotVideoItemModel> list = (loadingState.value as Success).response; List<HotVideoItemModel> list = (loadingState.value as Success).response;
for (HotVideoItemModel item in list) { for (HotVideoItemModel item in list) {
@@ -182,7 +183,9 @@ class LaterController extends MultiSelectController {
'videoItem': item, 'videoItem': item,
'heroTag': heroTag, 'heroTag': heroTag,
'sourceType': 'watchLater', 'sourceType': 'watchLater',
'count': (loadingState.value as Success).response.length, 'count': list.length,
'favTitle': '稍后再看',
'mediaId': GStorage.userInfo.get('userInfoCache')?.mid,
}, },
); );
break; break;

View File

@@ -6,6 +6,7 @@ import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/video/member_video_ctr.dart'; import 'package:PiliPalaX/pages/member/new/content/member_contribute/content/video/member_video_ctr.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/pages/member/new/controller.dart';
import 'package:PiliPalaX/pages/video/detail/reply/view.dart'; import 'package:PiliPalaX/pages/video/detail/reply/view.dart';
import 'package:PiliPalaX/utils/grid.dart'; import 'package:PiliPalaX/utils/grid.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -19,6 +20,7 @@ class MemberVideo extends StatefulWidget {
required this.mid, required this.mid,
this.seasonId, this.seasonId,
this.seriesId, this.seriesId,
this.title,
}); });
final ContributeType type; final ContributeType type;
@@ -26,6 +28,7 @@ class MemberVideo extends StatefulWidget {
final int mid; final int mid;
final int? seasonId; final int? seasonId;
final int? seriesId; final int? seriesId;
final String? title;
@override @override
State<MemberVideo> createState() => _MemberVideoState(); State<MemberVideo> createState() => _MemberVideoState();
@@ -42,6 +45,8 @@ class _MemberVideoState extends State<MemberVideo>
mid: widget.mid, mid: widget.mid,
seasonId: widget.seasonId, seasonId: widget.seasonId,
seriesId: widget.seriesId, seriesId: widget.seriesId,
username: Get.find<MemberControllerNew>(tag: widget.heroTag).username,
title: widget.title,
), ),
tag: tag:
'${widget.heroTag}${widget.type.name}${widget.seasonId}${widget.seriesId}', '${widget.heroTag}${widget.type.name}${widget.seasonId}${widget.seriesId}',
@@ -72,7 +77,6 @@ class _MemberVideoState extends State<MemberVideo>
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0), padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Obx( Obx(
() => Text( () => Text(
@@ -82,6 +86,33 @@ class _MemberVideoState extends State<MemberVideo>
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
), ),
), ),
if (_controller.episodicButton != null) ...[
const SizedBox(width: 5),
SizedBox(
height: 35,
child: TextButton.icon(
onPressed: _controller.toViewPlayAll,
icon: Icon(
Icons.play_circle_outline_rounded,
size: 16,
color:
Theme.of(context).colorScheme.secondary,
),
label: Text(
'播放全部',
style: TextStyle(
fontSize: 13,
color: Theme.of(context)
.colorScheme
.secondary,
),
), // TODO: continue playing
// label: Text(
// '${_controller.episodicButton?.text}'),
),
),
],
const Spacer(),
SizedBox( SizedBox(
height: 35, height: 35,
child: TextButton.icon( child: TextButton.icon(
@@ -110,7 +141,7 @@ class _MemberVideoState extends State<MemberVideo>
), ),
), ),
), ),
) ),
], ],
), ),
), ),

View File

@@ -1,9 +1,14 @@
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/http/member.dart'; import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/models/space_archive/data.dart'; import 'package:PiliPalaX/models/space_archive/data.dart';
import 'package:PiliPalaX/models/space_archive/episodic_button.dart';
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/id_utils.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class MemberVideoCtr extends CommonController { class MemberVideoCtr extends CommonController {
@@ -12,6 +17,8 @@ class MemberVideoCtr extends CommonController {
required this.mid, required this.mid,
required this.seasonId, required this.seasonId,
required this.seriesId, required this.seriesId,
this.username,
this.title,
}); });
ContributeType type; ContributeType type;
@@ -23,6 +30,9 @@ class MemberVideoCtr extends CommonController {
RxString sort = 'desc'.obs; RxString sort = 'desc'.obs;
RxInt count = (-1).obs; RxInt count = (-1).obs;
int? next; int? next;
EpisodicButton? episodicButton;
final String? username;
final String? title;
@override @override
Future onRefresh() async { Future onRefresh() async {
@@ -43,6 +53,7 @@ class MemberVideoCtr extends CommonController {
@override @override
bool customHandleResponse(Success response) { bool customHandleResponse(Success response) {
Data data = response.response; Data data = response.response;
episodicButton = data.episodicButton;
next = data.next; next = data.next;
aid = data.item?.lastOrNull?.param; aid = data.item?.lastOrNull?.param;
isEnd = isEnd =
@@ -80,4 +91,34 @@ class MemberVideoCtr extends CommonController {
loadingState.value = LoadingState.loading(); loadingState.value = LoadingState.loading();
onRefresh(); onRefresh();
} }
void toViewPlayAll() {
if (loadingState.value is Success) {
List<Item> list = (loadingState.value as Success).response;
for (Item element in list) {
if (element.firstCid == null) {
continue;
} else {
if (element.bvid != list.first.bvid) {
SmartDialog.showToast('已跳过不支持播放的视频');
}
final String heroTag = Utils.makeHeroTag(element.bvid);
Get.toNamed(
'/video?bvid=${element.bvid}&cid=${element.firstCid}',
arguments: {
'videoItem': element,
'heroTag': heroTag,
'sourceType': 'archive',
'mediaId': seasonId ?? seriesId ?? mid,
'oid': IdUtils.bv2av(element.bvid!), // TODO: continue playing
'favTitle':
'$username: ${title ?? episodicButton?.text ?? '播放全部'}',
'count': count.value,
},
);
break;
}
}
}
}
} }

View File

@@ -83,11 +83,13 @@ class _MemberContributeState extends State<MemberContribute>
type: ContributeType.video, type: ContributeType.video,
heroTag: widget.heroTag, heroTag: widget.heroTag,
mid: widget.mid, mid: widget.mid,
title: item.title,
), ),
'charging_video' => MemberVideo( 'charging_video' => MemberVideo(
type: ContributeType.charging, type: ContributeType.charging,
heroTag: widget.heroTag, heroTag: widget.heroTag,
mid: widget.mid, mid: widget.mid,
title: item.title,
), ),
'article' => MemberArticle( 'article' => MemberArticle(
heroTag: widget.heroTag, heroTag: widget.heroTag,
@@ -99,12 +101,14 @@ class _MemberContributeState extends State<MemberContribute>
heroTag: widget.heroTag, heroTag: widget.heroTag,
mid: widget.mid, mid: widget.mid,
seasonId: item.seasonId, seasonId: item.seasonId,
title: item.title,
), ),
'series' => MemberVideo( 'series' => MemberVideo(
type: ContributeType.series, type: ContributeType.series,
heroTag: widget.heroTag, heroTag: widget.heroTag,
mid: widget.mid, mid: widget.mid,
seriesId: item.seriesId, seriesId: item.seriesId,
title: item.title,
), ),
_ => Center(child: Text(item.title!)) _ => Center(child: Text(item.title!))
}, },
@@ -118,6 +122,7 @@ class _MemberContributeState extends State<MemberContribute>
type: ContributeType.video, type: ContributeType.video,
heroTag: widget.heroTag, heroTag: widget.heroTag,
mid: widget.mid, mid: widget.mid,
title: '视频',
); );
} }
} }

View File

@@ -244,11 +244,16 @@ class VideoDetailController extends GetxController
} }
// 页面来源 稍后再看 收藏夹 // 页面来源 稍后再看 收藏夹
RxString sourceType = 'normal'.obs; String sourceType = 'normal';
late RxList<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[].obs; late RxList<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[].obs;
late RxString watchLaterTitle = ''.obs; late String watchLaterTitle = '';
bool get isPlayAll => bool get isPlayAll => ['watchLater', 'fav', 'archive'].contains(sourceType);
sourceType.value == 'watchLater' || sourceType.value == 'fav'; int get _mediaType => switch (sourceType) {
'archive' => 1,
'watchLater' => 2,
'fav' => 3,
_ => -1,
};
@override @override
void onInit() { void onInit() {
@@ -258,23 +263,24 @@ class VideoDetailController extends GetxController
if (keys.isNotEmpty) { if (keys.isNotEmpty) {
if (keys.contains('videoItem')) { if (keys.contains('videoItem')) {
var args = Get.arguments['videoItem']; var args = Get.arguments['videoItem'];
if (args.pic != null && args.pic != '') { try {
videoItem['pic'] = args.pic; if (args.pic != null && args.pic != '') {
} videoItem['pic'] = args.pic;
} else if (args.cover != null && args.cover != '') {
videoItem['pic'] = args.cover;
}
} catch (_) {}
} }
if (keys.contains('pic')) { if (keys.contains('pic')) {
videoItem['pic'] = Get.arguments['pic']; videoItem['pic'] = Get.arguments['pic'];
} }
} }
sourceType.value = Get.arguments['sourceType'] ?? 'normal'; sourceType = Get.arguments['sourceType'] ?? 'normal';
if (sourceType.value == 'watchLater') { if (sourceType != 'normal') {
watchLaterTitle.value = '稍后再看'; watchLaterTitle = Get.arguments['favTitle'];
fetchMediaList(); getMediaList();
} else if (sourceType.value == 'fav') {
watchLaterTitle.value = Get.arguments['favTitle'];
queryFavVideoList();
} }
bool defaultShowComment = bool defaultShowComment =
@@ -322,31 +328,17 @@ class VideoDetailController extends GetxController
} }
} }
// 获取稍后再看列表 void getMediaList() async {
Future fetchMediaList() async {
var count = Get.arguments['count'];
var res = await UserHttp.getMediaList(
type: 2,
bizId: userInfo.mid,
ps: count,
);
if (res['status']) {
mediaList = res['data'].reversed.toList();
} else {
SmartDialog.showToast(res['msg']);
}
}
void loadMoreMedia() async {
if (mediaList.length >= Get.arguments['count']) { if (mediaList.length >= Get.arguments['count']) {
return; return;
} }
var res = await UserHttp.getMediaList( var res = await UserHttp.getMediaList(
type: 3, type: _mediaType,
bizId: Get.arguments['mediaId'] ?? -1, bizId: Get.arguments['mediaId'] ?? -1,
ps: 20, ps: 20,
oid: mediaList.last.id, oid: mediaList.isEmpty ? null : mediaList.last.id,
otype: mediaList.last.type ?? 2, otype: mediaList.isEmpty ? null : mediaList.last.type,
desc: _mediaType == 2 ? false : true,
); );
if (res['status']) { if (res['status']) {
if (res['data'].isNotEmpty) { if (res['data'].isNotEmpty) {
@@ -364,10 +356,10 @@ class VideoDetailController extends GetxController
(context) => MediaListPanel( (context) => MediaListPanel(
mediaList: mediaList, mediaList: mediaList,
changeMediaList: changeMediaList, changeMediaList: changeMediaList,
panelTitle: watchLaterTitle.value, panelTitle: watchLaterTitle,
bvid: bvid, bvid: bvid,
count: Get.arguments['count'], count: Get.arguments['count'],
loadMoreMedia: loadMoreMedia, loadMoreMedia: getMediaList,
), ),
); );
} }
@@ -402,20 +394,6 @@ class VideoDetailController extends GetxController
} catch (_) {} } catch (_) {}
} }
// 获取收藏夹视频列表
Future queryFavVideoList() async {
var mediaId = Get.arguments['mediaId'];
var oid = Get.arguments['oid'];
var res = await UserHttp.parseFavVideo(
mediaId: mediaId,
oid: oid,
bvid: bvid,
);
if (res['status']) {
mediaList.value = res['data'];
}
}
int? _lastPos; int? _lastPos;
double? _blockLimit; double? _blockLimit;
List<Pair<SegmentType, SkipType>>? _blockSettings; List<Pair<SegmentType, SkipType>>? _blockSettings;

View File

@@ -95,7 +95,13 @@ class VideoIntroController extends GetxController
preRender = true; preRender = true;
var args = Get.arguments['videoItem']; var args = Get.arguments['videoItem'];
var keys = Get.arguments.keys.toList(); var keys = Get.arguments.keys.toList();
videoItem!['pic'] = args.pic; try {
if (args.pic != null && args.pic != '') {
videoItem!['pic'] = args.pic;
} else if (args.cover != null && args.cover != '') {
videoItem!['pic'] = args.cover;
}
} catch (_) {}
if (args.title is String) { if (args.title is String) {
videoItem!['title'] = args.title; videoItem!['title'] = args.title;
} else { } else {
@@ -693,7 +699,7 @@ class VideoIntroController extends GetxController
if (videoDetailController.isPlayAll && if (videoDetailController.isPlayAll &&
currentIndex == episodes.length - 2) { currentIndex == episodes.length - 2) {
videoDetailCtr.loadMoreMedia(); videoDetailCtr.getMediaList();
} }
// 列表循环 // 列表循环

View File

@@ -1415,46 +1415,44 @@ class _VideoDetailPageState extends State<VideoDetailPage>
return Stack( return Stack(
children: [ children: [
introPanel(), introPanel(),
Obx( AnimatedPositioned(
() => AnimatedPositioned( duration: const Duration(milliseconds: 400),
duration: const Duration(milliseconds: 400), curve: Curves.easeInOut,
curve: Curves.easeInOut, left: 12,
left: 12, right: 12,
right: 12, bottom: MediaQuery.of(context).padding.bottom + 12,
bottom: MediaQuery.of(context).padding.bottom + 12, child: Material(
child: Material( color: Colors.transparent,
color: Colors.transparent, child: InkWell(
child: InkWell( onTap: videoDetailController.showMediaListPanel,
onTap: videoDetailController.showMediaListPanel, borderRadius: const BorderRadius.all(Radius.circular(14)),
borderRadius: const BorderRadius.all(Radius.circular(14)), child: Container(
child: Container( height: 54,
height: 54, padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration(
decoration: BoxDecoration( color: Theme.of(context)
color: Theme.of(context) .colorScheme
.colorScheme .secondaryContainer
.secondaryContainer .withOpacity(0.95),
.withOpacity(0.95), borderRadius: const BorderRadius.all(Radius.circular(14)),
borderRadius: const BorderRadius.all(Radius.circular(14)), ),
), child: Row(
child: Row( children: [
children: [ const Icon(Icons.playlist_play, size: 24),
const Icon(Icons.playlist_play, size: 24), const SizedBox(width: 10),
const SizedBox(width: 10), Text(
Text( videoDetailController.watchLaterTitle,
videoDetailController.watchLaterTitle.value, style: TextStyle(
style: TextStyle( color: Theme.of(context)
color: Theme.of(context) .colorScheme
.colorScheme .onSecondaryContainer,
.onSecondaryContainer, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, letterSpacing: 0.2,
letterSpacing: 0.2,
),
), ),
const Spacer(), ),
const Icon(Icons.keyboard_arrow_up_rounded, size: 26), const Spacer(),
], const Icon(Icons.keyboard_arrow_up_rounded, size: 26),
), ],
), ),
), ),
), ),