mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: sort fav folder
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -116,6 +116,8 @@ class Api {
|
|||||||
|
|
||||||
static const String sortFav = '/x/v3/fav/resource/sort';
|
static const String sortFav = '/x/v3/fav/resource/sort';
|
||||||
|
|
||||||
|
static const String sortFavFolder = '/x/v3/fav/folder/sort';
|
||||||
|
|
||||||
// 判断视频是否被收藏(双端)GET
|
// 判断视频是否被收藏(双端)GET
|
||||||
/// aid
|
/// aid
|
||||||
// https://api.bilibili.com/x/v2/fav/video/favoured
|
// https://api.bilibili.com/x/v2/fav/video/favoured
|
||||||
|
|||||||
@@ -66,6 +66,28 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future sortFavFolder({
|
||||||
|
required List<int?> sort,
|
||||||
|
}) async {
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
'sort': sort.join(','),
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
};
|
||||||
|
Utils.appSign(data);
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.sortFavFolder,
|
||||||
|
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 sortFav({
|
static Future sortFav({
|
||||||
required dynamic mediaId,
|
required dynamic mediaId,
|
||||||
required List<String> sort,
|
required List<String> sort,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class FavController
|
|||||||
@override
|
@override
|
||||||
Future<LoadingState<FavFolderData>> customGetData() => UserHttp.userfavFolder(
|
Future<LoadingState<FavFolderData>> customGetData() => UserHttp.userfavFolder(
|
||||||
pn: currentPage,
|
pn: currentPage,
|
||||||
ps: 10,
|
ps: 20,
|
||||||
mid: mid,
|
mid: mid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
138
lib/pages/fav/video/fav_folder_sort_page.dart
Normal file
138
lib/pages/fav/video/fav_folder_sort_page.dart
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
|
import 'package:PiliPlus/http/user.dart';
|
||||||
|
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||||
|
import 'package:PiliPlus/pages/fav/video/controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/fav/video/widgets/item.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 FavFolderSortPage extends StatefulWidget {
|
||||||
|
const FavFolderSortPage({super.key, required this.favController});
|
||||||
|
|
||||||
|
final FavController favController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FavFolderSortPage> createState() => _FavFolderSortPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FavFolderSortPageState extends State<FavFolderSortPage> {
|
||||||
|
FavController get _favController => widget.favController;
|
||||||
|
|
||||||
|
final GlobalKey _key = GlobalKey();
|
||||||
|
late List<FavFolderItemData> sortList = List<FavFolderItemData>.from(
|
||||||
|
(_favController.loadingState.value as Success).response);
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
void listener() {
|
||||||
|
if (_favController.isEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent - 200) {
|
||||||
|
_favController.onLoadMore().then((_) {
|
||||||
|
try {
|
||||||
|
if (_favController.loadingState.value is Success) {
|
||||||
|
List<FavFolderItemData> list =
|
||||||
|
(_favController.loadingState.value as Success).response;
|
||||||
|
sortList.addAll(list.sublist(sortList.length));
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (_favController.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('收藏夹排序'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
dynamic res = await UserHttp.sortFavFolder(
|
||||||
|
sort: sortList.map((item) => item.id).toList(),
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
SmartDialog.showToast('排序完成');
|
||||||
|
_favController.loadingState.value =
|
||||||
|
LoadingState.success(sortList);
|
||||||
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('完成'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: _buildBody,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onReorder(int oldIndex, int newIndex) {
|
||||||
|
if (oldIndex == 0 || newIndex == 0) {
|
||||||
|
SmartDialog.showToast('默认收藏夹不支持排序');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tabsItem = sortList.removeAt(oldIndex);
|
||||||
|
sortList.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.generate(
|
||||||
|
sortList.length,
|
||||||
|
(index) {
|
||||||
|
final item = sortList[index];
|
||||||
|
final key = item.id.toString();
|
||||||
|
return FavItem(
|
||||||
|
key: Key(key),
|
||||||
|
heroTag: key,
|
||||||
|
favFolderItem: item,
|
||||||
|
onLongPress: index == 0
|
||||||
|
? () {
|
||||||
|
SmartDialog.showToast('默认收藏夹不支持排序');
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,13 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
|||||||
class FavItem extends StatelessWidget {
|
class FavItem extends StatelessWidget {
|
||||||
final String heroTag;
|
final String heroTag;
|
||||||
final dynamic favFolderItem;
|
final dynamic favFolderItem;
|
||||||
final GestureTapCallback onTap;
|
final VoidCallback? onTap;
|
||||||
|
final VoidCallback? onLongPress;
|
||||||
|
|
||||||
const FavItem({
|
const FavItem({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onTap,
|
this.onTap,
|
||||||
|
this.onLongPress,
|
||||||
required this.heroTag,
|
required this.heroTag,
|
||||||
required this.favFolderItem,
|
required this.favFolderItem,
|
||||||
});
|
});
|
||||||
@@ -19,11 +22,14 @@ class FavItem extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onLongPress: () => imageSaveDialog(
|
onLongPress: onLongPress ??
|
||||||
context: context,
|
(onTap == null
|
||||||
title: favFolderItem.title,
|
? null
|
||||||
cover: favFolderItem.cover,
|
: () => imageSaveDialog(
|
||||||
),
|
context: context,
|
||||||
|
title: favFolderItem.title,
|
||||||
|
cover: favFolderItem.cover,
|
||||||
|
)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:PiliPlus/models/user/fav_folder.dart';
|
|||||||
import 'package:PiliPlus/pages/fav/article/view.dart';
|
import 'package:PiliPlus/pages/fav/article/view.dart';
|
||||||
import 'package:PiliPlus/pages/fav/note/view.dart';
|
import 'package:PiliPlus/pages/fav/note/view.dart';
|
||||||
import 'package:PiliPlus/pages/fav/pgc/view.dart';
|
import 'package:PiliPlus/pages/fav/pgc/view.dart';
|
||||||
|
import 'package:PiliPlus/pages/fav/video/fav_folder_sort_page.dart';
|
||||||
import 'package:PiliPlus/pages/fav/video/index.dart';
|
import 'package:PiliPlus/pages/fav/video/index.dart';
|
||||||
import 'package:PiliPlus/pages/fav_search/view.dart';
|
import 'package:PiliPlus/pages/fav_search/view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -31,15 +32,29 @@ class FavPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
|
class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
|
||||||
late final TabController _tabController = TabController(
|
late final TabController _tabController;
|
||||||
length: _FavType.values.length,
|
|
||||||
vsync: this,
|
|
||||||
initialIndex: Get.arguments is int ? Get.arguments : 0,
|
|
||||||
);
|
|
||||||
final FavController _favController = Get.put(FavController());
|
final FavController _favController = Get.put(FavController());
|
||||||
|
late final RxInt _tabIndex;
|
||||||
|
|
||||||
|
void listener() {
|
||||||
|
_tabIndex.value = _tabController.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_tabIndex = (Get.arguments is int ? Get.arguments as int : 0).obs;
|
||||||
|
_tabController = TabController(
|
||||||
|
length: _FavType.values.length,
|
||||||
|
vsync: this,
|
||||||
|
initialIndex: _tabIndex.value,
|
||||||
|
);
|
||||||
|
_tabController.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_tabController.removeListener(listener);
|
||||||
_tabController.dispose();
|
_tabController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -50,48 +65,68 @@ class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('我的收藏'),
|
title: const Text('我的收藏'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
Obx(
|
||||||
onPressed: () {
|
() => _tabIndex.value == 0
|
||||||
Get.toNamed('/createFav')?.then(
|
? IconButton(
|
||||||
(data) {
|
onPressed: () {
|
||||||
if (data != null) {
|
Get.toNamed('/createFav')?.then(
|
||||||
List<FavFolderItemData> list =
|
(data) {
|
||||||
_favController.loadingState.value is Success
|
if (data != null) {
|
||||||
? (_favController.loadingState.value as Success)
|
List<FavFolderItemData> list = _favController
|
||||||
.response
|
.loadingState.value is Success
|
||||||
: <FavFolderItemData>[];
|
? (_favController.loadingState.value as Success)
|
||||||
list.insert(list.isNotEmpty ? 1 : 0, data);
|
.response
|
||||||
_favController.loadingState.refresh();
|
: <FavFolderItemData>[];
|
||||||
}
|
list.insert(list.isNotEmpty ? 1 : 0, data);
|
||||||
},
|
_favController.loadingState.refresh();
|
||||||
);
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
);
|
||||||
tooltip: '新建收藏夹',
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (_favController.loadingState.value is Success) {
|
|
||||||
try {
|
|
||||||
final item = (_favController.loadingState.value as Success)
|
|
||||||
.response
|
|
||||||
.first;
|
|
||||||
Get.toNamed(
|
|
||||||
'/favSearch',
|
|
||||||
arguments: {
|
|
||||||
'type': 1,
|
|
||||||
'mediaId': item.id,
|
|
||||||
'title': item.title,
|
|
||||||
'count': item.mediaCount,
|
|
||||||
'searchType': SearchType.fav,
|
|
||||||
'isOwner': true,
|
|
||||||
},
|
},
|
||||||
);
|
icon: const Icon(Icons.add),
|
||||||
} catch (_) {}
|
tooltip: '新建收藏夹',
|
||||||
}
|
)
|
||||||
},
|
: const SizedBox.shrink(),
|
||||||
icon: const Icon(Icons.search_outlined),
|
),
|
||||||
tooltip: '搜索',
|
Obx(
|
||||||
|
() => _tabIndex.value == 0
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.to(FavFolderSortPage(favController: _favController));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.sort),
|
||||||
|
tooltip: '收藏夹排序',
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => _tabIndex.value == 0
|
||||||
|
? IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (_favController.loadingState.value is Success) {
|
||||||
|
try {
|
||||||
|
final item =
|
||||||
|
(_favController.loadingState.value as Success)
|
||||||
|
.response
|
||||||
|
.first;
|
||||||
|
Get.toNamed(
|
||||||
|
'/favSearch',
|
||||||
|
arguments: {
|
||||||
|
'type': 1,
|
||||||
|
'mediaId': item.id,
|
||||||
|
'title': item.title,
|
||||||
|
'count': item.mediaCount,
|
||||||
|
'searchType': SearchType.fav,
|
||||||
|
'isOwner': true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
tooltip: '搜索',
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class _FavSortPageState extends State<FavSortPage> {
|
|||||||
if (_favDetailController.loadingState.value is Success) {
|
if (_favDetailController.loadingState.value is Success) {
|
||||||
List<FavDetailItemData> list =
|
List<FavDetailItemData> list =
|
||||||
(_favDetailController.loadingState.value as Success).response;
|
(_favDetailController.loadingState.value as Success).response;
|
||||||
this.sortList.addAll(list.sublist(this.sortList.length));
|
sortList.addAll(list.sublist(sortList.length));
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user