mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: play all member archives
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/video/later.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/model_hot_video_item.dart';
|
||||
import '../models/user/fav_detail.dart';
|
||||
@@ -517,7 +514,9 @@ class UserHttp {
|
||||
required int bizId,
|
||||
required int ps,
|
||||
int? oid,
|
||||
int otype = 2,
|
||||
int? otype,
|
||||
bool withCurrent = false,
|
||||
bool desc = true,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.mediaList,
|
||||
@@ -525,14 +524,14 @@ class UserHttp {
|
||||
'mobi_app': 'web',
|
||||
'type': type,
|
||||
'biz_id': bizId,
|
||||
'oid': oid ?? '',
|
||||
'otype': otype, // video:2 // bangumi: 24
|
||||
if (oid != null) 'oid': oid,
|
||||
if (otype != null) 'otype': otype, // video:2 // bangumi: 24
|
||||
'ps': ps,
|
||||
'direction': false,
|
||||
'desc': true,
|
||||
'desc': desc,
|
||||
'sort_field': 1,
|
||||
'tid': 0,
|
||||
'with_current': false,
|
||||
'with_current': withCurrent,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -551,30 +550,30 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 解析收藏夹视频
|
||||
static Future parseFavVideo({
|
||||
required int mediaId,
|
||||
required int oid,
|
||||
required String bvid,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
'https://www.bilibili.com/list/ml$mediaId',
|
||||
queryParameters: {
|
||||
'oid': mediaId,
|
||||
'bvid': bvid,
|
||||
},
|
||||
);
|
||||
String scriptContent =
|
||||
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
int startIndex = scriptContent.indexOf('{');
|
||||
int endIndex = scriptContent.lastIndexOf('};');
|
||||
String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||
// 解析JSON字符串为Map
|
||||
Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
return {
|
||||
'status': true,
|
||||
'data': jsonData['resourceList']
|
||||
.map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||
.toList()
|
||||
};
|
||||
}
|
||||
// static Future parseFavVideo({
|
||||
// required int mediaId,
|
||||
// required int oid,
|
||||
// required String bvid,
|
||||
// }) async {
|
||||
// var res = await Request().get(
|
||||
// 'https://www.bilibili.com/list/ml$mediaId',
|
||||
// queryParameters: {
|
||||
// 'oid': mediaId,
|
||||
// 'bvid': bvid,
|
||||
// },
|
||||
// );
|
||||
// String scriptContent =
|
||||
// extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
// int startIndex = scriptContent.indexOf('{');
|
||||
// int endIndex = scriptContent.lastIndexOf('};');
|
||||
// String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||
// // 解析JSON字符串为Map
|
||||
// Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
// return {
|
||||
// 'status': true,
|
||||
// 'data': jsonData['resourceList']
|
||||
// .map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||
// .toList()
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class FavDetailController extends MultiSelectController {
|
||||
);
|
||||
}
|
||||
|
||||
Future toViewPlayAll() async {
|
||||
void toViewPlayAll() {
|
||||
if (loadingState.value is Success) {
|
||||
List<FavDetailItemData> list = (loadingState.value as Success).response;
|
||||
for (FavDetailItemData element in list) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/model_hot_video_item.dart';
|
||||
import 'package:PiliPalaX/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:flutter/material.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) {
|
||||
List<HotVideoItemModel> list = (loadingState.value as Success).response;
|
||||
for (HotVideoItemModel item in list) {
|
||||
@@ -182,7 +183,9 @@ class LaterController extends MultiSelectController {
|
||||
'videoItem': item,
|
||||
'heroTag': heroTag,
|
||||
'sourceType': 'watchLater',
|
||||
'count': (loadingState.value as Success).response.length,
|
||||
'count': list.length,
|
||||
'favTitle': '稍后再看',
|
||||
'mediaId': GStorage.userInfo.get('userInfoCache')?.mid,
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -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/member_contribute.dart'
|
||||
show ContributeType;
|
||||
import 'package:PiliPalaX/pages/member/new/controller.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply/view.dart';
|
||||
import 'package:PiliPalaX/utils/grid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -19,6 +20,7 @@ class MemberVideo extends StatefulWidget {
|
||||
required this.mid,
|
||||
this.seasonId,
|
||||
this.seriesId,
|
||||
this.title,
|
||||
});
|
||||
|
||||
final ContributeType type;
|
||||
@@ -26,6 +28,7 @@ class MemberVideo extends StatefulWidget {
|
||||
final int mid;
|
||||
final int? seasonId;
|
||||
final int? seriesId;
|
||||
final String? title;
|
||||
|
||||
@override
|
||||
State<MemberVideo> createState() => _MemberVideoState();
|
||||
@@ -42,6 +45,8 @@ class _MemberVideoState extends State<MemberVideo>
|
||||
mid: widget.mid,
|
||||
seasonId: widget.seasonId,
|
||||
seriesId: widget.seriesId,
|
||||
username: Get.find<MemberControllerNew>(tag: widget.heroTag).username,
|
||||
title: widget.title,
|
||||
),
|
||||
tag:
|
||||
'${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),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
@@ -82,6 +86,33 @@ class _MemberVideoState extends State<MemberVideo>
|
||||
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(
|
||||
height: 35,
|
||||
child: TextButton.icon(
|
||||
@@ -110,7 +141,7 @@ class _MemberVideoState extends State<MemberVideo>
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/member.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/member/new/content/member_contribute/member_contribute.dart'
|
||||
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';
|
||||
|
||||
class MemberVideoCtr extends CommonController {
|
||||
@@ -12,6 +17,8 @@ class MemberVideoCtr extends CommonController {
|
||||
required this.mid,
|
||||
required this.seasonId,
|
||||
required this.seriesId,
|
||||
this.username,
|
||||
this.title,
|
||||
});
|
||||
|
||||
ContributeType type;
|
||||
@@ -23,6 +30,9 @@ class MemberVideoCtr extends CommonController {
|
||||
RxString sort = 'desc'.obs;
|
||||
RxInt count = (-1).obs;
|
||||
int? next;
|
||||
EpisodicButton? episodicButton;
|
||||
final String? username;
|
||||
final String? title;
|
||||
|
||||
@override
|
||||
Future onRefresh() async {
|
||||
@@ -43,6 +53,7 @@ class MemberVideoCtr extends CommonController {
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
Data data = response.response;
|
||||
episodicButton = data.episodicButton;
|
||||
next = data.next;
|
||||
aid = data.item?.lastOrNull?.param;
|
||||
isEnd =
|
||||
@@ -80,4 +91,34 @@ class MemberVideoCtr extends CommonController {
|
||||
loadingState.value = LoadingState.loading();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +83,13 @@ class _MemberContributeState extends State<MemberContribute>
|
||||
type: ContributeType.video,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
title: item.title,
|
||||
),
|
||||
'charging_video' => MemberVideo(
|
||||
type: ContributeType.charging,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
title: item.title,
|
||||
),
|
||||
'article' => MemberArticle(
|
||||
heroTag: widget.heroTag,
|
||||
@@ -99,12 +101,14 @@ class _MemberContributeState extends State<MemberContribute>
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
seasonId: item.seasonId,
|
||||
title: item.title,
|
||||
),
|
||||
'series' => MemberVideo(
|
||||
type: ContributeType.series,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
seriesId: item.seriesId,
|
||||
title: item.title,
|
||||
),
|
||||
_ => Center(child: Text(item.title!))
|
||||
},
|
||||
@@ -118,6 +122,7 @@ class _MemberContributeState extends State<MemberContribute>
|
||||
type: ContributeType.video,
|
||||
heroTag: widget.heroTag,
|
||||
mid: widget.mid,
|
||||
title: '视频',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,11 +244,16 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
|
||||
// 页面来源 稍后再看 收藏夹
|
||||
RxString sourceType = 'normal'.obs;
|
||||
String sourceType = 'normal';
|
||||
late RxList<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[].obs;
|
||||
late RxString watchLaterTitle = ''.obs;
|
||||
bool get isPlayAll =>
|
||||
sourceType.value == 'watchLater' || sourceType.value == 'fav';
|
||||
late String watchLaterTitle = '';
|
||||
bool get isPlayAll => ['watchLater', 'fav', 'archive'].contains(sourceType);
|
||||
int get _mediaType => switch (sourceType) {
|
||||
'archive' => 1,
|
||||
'watchLater' => 2,
|
||||
'fav' => 3,
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -258,23 +263,24 @@ class VideoDetailController extends GetxController
|
||||
if (keys.isNotEmpty) {
|
||||
if (keys.contains('videoItem')) {
|
||||
var args = Get.arguments['videoItem'];
|
||||
if (args.pic != null && args.pic != '') {
|
||||
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 (keys.contains('pic')) {
|
||||
videoItem['pic'] = Get.arguments['pic'];
|
||||
}
|
||||
}
|
||||
|
||||
sourceType.value = Get.arguments['sourceType'] ?? 'normal';
|
||||
sourceType = Get.arguments['sourceType'] ?? 'normal';
|
||||
|
||||
if (sourceType.value == 'watchLater') {
|
||||
watchLaterTitle.value = '稍后再看';
|
||||
fetchMediaList();
|
||||
} else if (sourceType.value == 'fav') {
|
||||
watchLaterTitle.value = Get.arguments['favTitle'];
|
||||
queryFavVideoList();
|
||||
if (sourceType != 'normal') {
|
||||
watchLaterTitle = Get.arguments['favTitle'];
|
||||
getMediaList();
|
||||
}
|
||||
|
||||
bool defaultShowComment =
|
||||
@@ -322,31 +328,17 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
}
|
||||
|
||||
// 获取稍后再看列表
|
||||
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 {
|
||||
void getMediaList() async {
|
||||
if (mediaList.length >= Get.arguments['count']) {
|
||||
return;
|
||||
}
|
||||
var res = await UserHttp.getMediaList(
|
||||
type: 3,
|
||||
type: _mediaType,
|
||||
bizId: Get.arguments['mediaId'] ?? -1,
|
||||
ps: 20,
|
||||
oid: mediaList.last.id,
|
||||
otype: mediaList.last.type ?? 2,
|
||||
oid: mediaList.isEmpty ? null : mediaList.last.id,
|
||||
otype: mediaList.isEmpty ? null : mediaList.last.type,
|
||||
desc: _mediaType == 2 ? false : true,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (res['data'].isNotEmpty) {
|
||||
@@ -364,10 +356,10 @@ class VideoDetailController extends GetxController
|
||||
(context) => MediaListPanel(
|
||||
mediaList: mediaList,
|
||||
changeMediaList: changeMediaList,
|
||||
panelTitle: watchLaterTitle.value,
|
||||
panelTitle: watchLaterTitle,
|
||||
bvid: bvid,
|
||||
count: Get.arguments['count'],
|
||||
loadMoreMedia: loadMoreMedia,
|
||||
loadMoreMedia: getMediaList,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -402,20 +394,6 @@ class VideoDetailController extends GetxController
|
||||
} 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;
|
||||
double? _blockLimit;
|
||||
List<Pair<SegmentType, SkipType>>? _blockSettings;
|
||||
|
||||
@@ -95,7 +95,13 @@ class VideoIntroController extends GetxController
|
||||
preRender = true;
|
||||
var args = Get.arguments['videoItem'];
|
||||
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) {
|
||||
videoItem!['title'] = args.title;
|
||||
} else {
|
||||
@@ -693,7 +699,7 @@ class VideoIntroController extends GetxController
|
||||
|
||||
if (videoDetailController.isPlayAll &&
|
||||
currentIndex == episodes.length - 2) {
|
||||
videoDetailCtr.loadMoreMedia();
|
||||
videoDetailCtr.getMediaList();
|
||||
}
|
||||
|
||||
// 列表循环
|
||||
|
||||
@@ -1415,46 +1415,44 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
return Stack(
|
||||
children: [
|
||||
introPanel(),
|
||||
Obx(
|
||||
() => AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 12,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: videoDetailController.showMediaListPanel,
|
||||
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.value,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 12,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: videoDetailController.showMediaListPanel,
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const Icon(Icons.keyboard_arrow_up_rounded, size: 26),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user