mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: sort fav
Closes #530 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -81,6 +81,18 @@ class Api {
|
|||||||
/// https://api.bilibili.com/x/web-interface/archive/coins
|
/// https://api.bilibili.com/x/web-interface/archive/coins
|
||||||
static const String hasCoinVideo = '/x/web-interface/archive/coins';
|
static const String hasCoinVideo = '/x/web-interface/archive/coins';
|
||||||
|
|
||||||
|
/// 收藏夹 详情
|
||||||
|
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
||||||
|
/// pn int 当前页
|
||||||
|
/// ps int pageSize
|
||||||
|
/// keyword String 搜索词
|
||||||
|
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
||||||
|
/// tid int 分区id
|
||||||
|
/// platform web
|
||||||
|
/// type 0 当前收藏夹 1 全部收藏夹
|
||||||
|
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
||||||
|
static const String favResourceList = '/x/v3/fav/resource/list';
|
||||||
|
|
||||||
// 收藏视频(双端)POST
|
// 收藏视频(双端)POST
|
||||||
// access_key str APP登录Token APP方式必要
|
// access_key str APP登录Token APP方式必要
|
||||||
/// rid num 稿件avid 必要
|
/// rid num 稿件avid 必要
|
||||||
@@ -96,6 +108,14 @@ class Api {
|
|||||||
|
|
||||||
static const String delFav = '/x/v3/fav/resource/batch-del';
|
static const String delFav = '/x/v3/fav/resource/batch-del';
|
||||||
|
|
||||||
|
static const String copyFav = '/x/v3/fav/resource/copy';
|
||||||
|
|
||||||
|
static const String moveFav = '/x/v3/fav/resource/move';
|
||||||
|
|
||||||
|
static const String cleanFav = '/x/v3/fav/resource/clean';
|
||||||
|
|
||||||
|
static const String sortFav = '/x/v3/fav/resource/sort';
|
||||||
|
|
||||||
// 判断视频是否被收藏(双端)GET
|
// 判断视频是否被收藏(双端)GET
|
||||||
/// aid
|
/// aid
|
||||||
// https://api.bilibili.com/x/v2/fav/video/favoured
|
// https://api.bilibili.com/x/v2/fav/video/favoured
|
||||||
@@ -123,10 +143,6 @@ class Api {
|
|||||||
// rid num 目标 视频稿件avid
|
// rid num 目标 视频稿件avid
|
||||||
static const String favFolder = '/x/v3/fav/folder/created/list-all';
|
static const String favFolder = '/x/v3/fav/folder/created/list-all';
|
||||||
|
|
||||||
static const String copyFav = '/x/v3/fav/resource/copy';
|
|
||||||
|
|
||||||
static const String moveFav = '/x/v3/fav/resource/move';
|
|
||||||
|
|
||||||
static const String copyToview = '/x/v2/history/toview/copy';
|
static const String copyToview = '/x/v2/history/toview/copy';
|
||||||
|
|
||||||
static const String moveToview = '/x/v2/history/toview/move';
|
static const String moveToview = '/x/v2/history/toview/move';
|
||||||
@@ -192,20 +208,6 @@ class Api {
|
|||||||
|
|
||||||
static const String deleteFolder = '/x/v3/fav/folder/del';
|
static const String deleteFolder = '/x/v3/fav/folder/del';
|
||||||
|
|
||||||
static const String cleanFav = '/x/v3/fav/resource/clean';
|
|
||||||
|
|
||||||
/// 收藏夹 详情
|
|
||||||
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
|
||||||
/// pn int 当前页
|
|
||||||
/// ps int pageSize
|
|
||||||
/// keyword String 搜索词
|
|
||||||
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
|
||||||
/// tid int 分区id
|
|
||||||
/// platform web
|
|
||||||
/// type 0 当前收藏夹 1 全部收藏夹
|
|
||||||
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
|
||||||
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
|
||||||
|
|
||||||
// 正在直播的up & 关注的up
|
// 正在直播的up & 关注的up
|
||||||
// https://api.bilibili.com/x/polymer/web-dynamic/v1/portal
|
// https://api.bilibili.com/x/polymer/web-dynamic/v1/portal
|
||||||
static const String followUp = '/x/polymer/web-dynamic/v1/portal';
|
static const String followUp = '/x/polymer/web-dynamic/v1/portal';
|
||||||
@@ -674,9 +676,6 @@ class Api {
|
|||||||
/// 我的订阅-合集详情
|
/// 我的订阅-合集详情
|
||||||
static const favSeasonList = '/x/space/fav/season/list';
|
static const favSeasonList = '/x/space/fav/season/list';
|
||||||
|
|
||||||
/// 我的订阅-播单详情
|
|
||||||
static const favResourceList = '/x/v3/fav/resource/list';
|
|
||||||
|
|
||||||
/// 发送私信
|
/// 发送私信
|
||||||
static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
|
static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models/video/later.dart';
|
import 'package:PiliPlus/models/video/later.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.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 '../common/constants.dart';
|
import '../common/constants.dart';
|
||||||
@@ -62,6 +63,30 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future sortFav({
|
||||||
|
required dynamic mediaId,
|
||||||
|
required List<String> sort,
|
||||||
|
}) async {
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
'media_id': mediaId,
|
||||||
|
'sort': sort.join(','),
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
};
|
||||||
|
Utils.appSign(data);
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.sortFav,
|
||||||
|
data: data,
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future cleanFav({
|
static Future cleanFav({
|
||||||
required dynamic mediaId,
|
required dynamic mediaId,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -152,7 +177,7 @@ class UserHttp {
|
|||||||
String keyword = '',
|
String keyword = '',
|
||||||
String order = 'mtime',
|
String order = 'mtime',
|
||||||
int type = 0}) async {
|
int type = 0}) async {
|
||||||
var res = await Request().get(Api.userFavFolderDetail, queryParameters: {
|
var res = await Request().get(Api.favResourceList, queryParameters: {
|
||||||
'media_id': mediaId,
|
'media_id': mediaId,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'ps': ps,
|
'ps': ps,
|
||||||
|
|||||||
145
lib/pages/fav_detail/fav_sort_page.dart
Normal file
145
lib/pages/fav_detail/fav_sort_page.dart
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import 'package:PiliPlus/build_config.dart';
|
||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/http/user.dart';
|
||||||
|
import 'package:PiliPlus/pages/fav_detail/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
|
||||||
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class FavSortPage extends StatefulWidget {
|
||||||
|
const FavSortPage({super.key, required this.favDetailController});
|
||||||
|
|
||||||
|
final FavDetailController favDetailController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FavSortPage> createState() => _FavSortPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FavSortPageState extends State<FavSortPage> {
|
||||||
|
FavDetailController get _favDetailController => widget.favDetailController;
|
||||||
|
|
||||||
|
final GlobalKey _key = GlobalKey();
|
||||||
|
late List list =
|
||||||
|
List.from((_favDetailController.loadingState.value as Success).response);
|
||||||
|
List<String> sort = <String>[];
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
void listener() {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent - 200) {
|
||||||
|
_favDetailController.onLoadMore().then((_) {
|
||||||
|
try {
|
||||||
|
if (_favDetailController.loadingState.value is Success) {
|
||||||
|
List list =
|
||||||
|
(_favDetailController.loadingState.value as Success).response;
|
||||||
|
this.list.addAll(list.sublist(this.list.length));
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (_favDetailController.isEnd.not) {
|
||||||
|
_scrollController.addListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.removeListener(listener);
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('排序: ${_favDetailController.item.value.title}'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (sort.isEmpty) {
|
||||||
|
Get.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dynamic res = await UserHttp.sortFav(
|
||||||
|
mediaId: _favDetailController.mediaId,
|
||||||
|
sort: sort,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
SmartDialog.showToast('排序完成');
|
||||||
|
_favDetailController.loadingState.value =
|
||||||
|
LoadingState.success(list);
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('完成'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _buildBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReorder(int oldIndex, int newIndex) {
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final oldItem = list[oldIndex];
|
||||||
|
final newItem =
|
||||||
|
list.getOrNull(oldIndex > newIndex ? newIndex - 1 : newIndex);
|
||||||
|
sort.add(
|
||||||
|
'${newItem == null ? '0:0' : '${newItem.id}:${newItem.type}'}:${oldItem.id}:${oldItem.type}');
|
||||||
|
|
||||||
|
final tabsItem = list.removeAt(oldIndex);
|
||||||
|
list.insert(newIndex, tabsItem);
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget get _buildBody {
|
||||||
|
return ReorderableListView(
|
||||||
|
key: _key,
|
||||||
|
scrollController: _scrollController,
|
||||||
|
onReorder: onReorder,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
footer: SizedBox(
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 80,
|
||||||
|
),
|
||||||
|
children: list
|
||||||
|
.map(
|
||||||
|
(item) => Stack(
|
||||||
|
key: Key(item.id.toString()),
|
||||||
|
children: [
|
||||||
|
FavVideoCardH(
|
||||||
|
isSort: true,
|
||||||
|
videoItem: item,
|
||||||
|
isOwner: false,
|
||||||
|
),
|
||||||
|
if (BuildConfig.isDebug)
|
||||||
|
Positioned(
|
||||||
|
top: 35,
|
||||||
|
right: 10,
|
||||||
|
child: Text(item.id.toString()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:PiliPlus/http/loading_state.dart';
|
|||||||
import 'package:PiliPlus/http/user.dart';
|
import 'package:PiliPlus/http/user.dart';
|
||||||
import 'package:PiliPlus/models/user/fav_detail.dart';
|
import 'package:PiliPlus/models/user/fav_detail.dart';
|
||||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||||
|
import 'package:PiliPlus/pages/fav_detail/fav_sort_page.dart';
|
||||||
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
|
import 'package:PiliPlus/pages/fav_search/view.dart' show SearchType;
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
@@ -240,6 +241,33 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
},
|
},
|
||||||
child: Text('清除失效内容'),
|
child: Text('清除失效内容'),
|
||||||
),
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
if (_favDetailController.loadingState
|
||||||
|
.value is Success &&
|
||||||
|
((_favDetailController
|
||||||
|
.loadingState
|
||||||
|
.value as Success)
|
||||||
|
.response as List?)
|
||||||
|
?.isNotEmpty ==
|
||||||
|
true) {
|
||||||
|
if ((_favDetailController.item.value
|
||||||
|
.mediaCount ??
|
||||||
|
0) >
|
||||||
|
1000) {
|
||||||
|
SmartDialog.showToast(
|
||||||
|
'内容太多啦!超过1000不支持排序');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.to(
|
||||||
|
FavSortPage(
|
||||||
|
favDetailController:
|
||||||
|
_favDetailController),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text('排序'),
|
||||||
|
),
|
||||||
if (!Utils.isDefault(_favDetailController
|
if (!Utils.isDefault(_favDetailController
|
||||||
.item.value.attr ??
|
.item.value.attr ??
|
||||||
0))
|
0))
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
final GestureTapCallback? onTap;
|
final GestureTapCallback? onTap;
|
||||||
final GestureLongPressCallback? onLongPress;
|
final GestureLongPressCallback? onLongPress;
|
||||||
final bool isOwner;
|
final bool isOwner;
|
||||||
final VoidCallback onViewFav;
|
final VoidCallback? onViewFav;
|
||||||
|
final bool? isSort;
|
||||||
|
|
||||||
const FavVideoCardH({
|
const FavVideoCardH({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -32,7 +33,8 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
this.isOwner = false,
|
this.isOwner = false,
|
||||||
required this.onViewFav,
|
this.onViewFav,
|
||||||
|
this.isSort,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -40,53 +42,57 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
int id = videoItem.id!;
|
int id = videoItem.id!;
|
||||||
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: isSort == true
|
||||||
if (onTap != null) {
|
? null
|
||||||
onTap!();
|
: () async {
|
||||||
return;
|
if (onTap != null) {
|
||||||
}
|
onTap!();
|
||||||
String? epId;
|
return;
|
||||||
if (videoItem.type == 24) {
|
}
|
||||||
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
String? epId;
|
||||||
dynamic seasonId = videoItem.ogv!['season_id'];
|
if (videoItem.type == 24) {
|
||||||
epId = videoItem.epId;
|
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
Utils.viewBangumi(seasonId: seasonId, epId: epId);
|
dynamic seasonId = videoItem.ogv!['season_id'];
|
||||||
return;
|
epId = videoItem.epId;
|
||||||
} else if (videoItem.page == 0 || videoItem.page! > 1) {
|
Utils.viewBangumi(seasonId: seasonId, epId: epId);
|
||||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
return;
|
||||||
if (result['status']) {
|
} else if (videoItem.page == 0 || videoItem.page! > 1) {
|
||||||
epId = result['data'].epId;
|
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||||
} else {
|
if (result['status']) {
|
||||||
SmartDialog.showToast(result['msg']);
|
epId = result['data'].epId;
|
||||||
}
|
} else {
|
||||||
}
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ([0, 16].contains(videoItem.attr).not) {
|
if ([0, 16].contains(videoItem.attr).not) {
|
||||||
Get.toNamed('/member?mid=${videoItem.owner.mid}');
|
Get.toNamed('/member?mid=${videoItem.owner.mid}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onViewFav();
|
onViewFav?.call();
|
||||||
// Utils.toViewPage(
|
// Utils.toViewPage(
|
||||||
// 'bvid=$bvid&cid=${videoItem.cid}${epId?.isNotEmpty == true ? '&epId=$epId' : ''}',
|
// 'bvid=$bvid&cid=${videoItem.cid}${epId?.isNotEmpty == true ? '&epId=$epId' : ''}',
|
||||||
// arguments: {
|
// arguments: {
|
||||||
// 'videoItem': videoItem,
|
// 'videoItem': videoItem,
|
||||||
// 'heroTag': Utils.makeHeroTag(id),
|
// 'heroTag': Utils.makeHeroTag(id),
|
||||||
// 'videoType':
|
// 'videoType':
|
||||||
// epId != null ? SearchType.media_bangumi : SearchType.video,
|
// epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||||
// },
|
// },
|
||||||
// );
|
// );
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: isSort == true
|
||||||
if (onLongPress != null) {
|
? null
|
||||||
onLongPress!();
|
: () {
|
||||||
} else {
|
if (onLongPress != null) {
|
||||||
imageSaveDialog(
|
onLongPress!();
|
||||||
context: context,
|
} else {
|
||||||
title: videoItem.title,
|
imageSaveDialog(
|
||||||
cover: videoItem.pic,
|
context: context,
|
||||||
);
|
title: videoItem.title,
|
||||||
}
|
cover: videoItem.pic,
|
||||||
},
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: StyleString.safeSpace,
|
horizontal: StyleString.safeSpace,
|
||||||
|
|||||||
Reference in New Issue
Block a user