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:
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/models/user/fav_detail.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/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
@@ -240,6 +241,33 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
},
|
||||
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
|
||||
.item.value.attr ??
|
||||
0))
|
||||
|
||||
@@ -22,7 +22,8 @@ class FavVideoCardH extends StatelessWidget {
|
||||
final GestureTapCallback? onTap;
|
||||
final GestureLongPressCallback? onLongPress;
|
||||
final bool isOwner;
|
||||
final VoidCallback onViewFav;
|
||||
final VoidCallback? onViewFav;
|
||||
final bool? isSort;
|
||||
|
||||
const FavVideoCardH({
|
||||
super.key,
|
||||
@@ -32,7 +33,8 @@ class FavVideoCardH extends StatelessWidget {
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.isOwner = false,
|
||||
required this.onViewFav,
|
||||
this.onViewFav,
|
||||
this.isSort,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -40,53 +42,57 @@ class FavVideoCardH extends StatelessWidget {
|
||||
int id = videoItem.id!;
|
||||
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
String? epId;
|
||||
if (videoItem.type == 24) {
|
||||
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
dynamic seasonId = videoItem.ogv!['season_id'];
|
||||
epId = videoItem.epId;
|
||||
Utils.viewBangumi(seasonId: seasonId, epId: epId);
|
||||
return;
|
||||
} else if (videoItem.page == 0 || videoItem.page! > 1) {
|
||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||
if (result['status']) {
|
||||
epId = result['data'].epId;
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
onTap: isSort == true
|
||||
? null
|
||||
: () async {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
String? epId;
|
||||
if (videoItem.type == 24) {
|
||||
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
dynamic seasonId = videoItem.ogv!['season_id'];
|
||||
epId = videoItem.epId;
|
||||
Utils.viewBangumi(seasonId: seasonId, epId: epId);
|
||||
return;
|
||||
} else if (videoItem.page == 0 || videoItem.page! > 1) {
|
||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||
if (result['status']) {
|
||||
epId = result['data'].epId;
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
if ([0, 16].contains(videoItem.attr).not) {
|
||||
Get.toNamed('/member?mid=${videoItem.owner.mid}');
|
||||
return;
|
||||
}
|
||||
onViewFav();
|
||||
// Utils.toViewPage(
|
||||
// 'bvid=$bvid&cid=${videoItem.cid}${epId?.isNotEmpty == true ? '&epId=$epId' : ''}',
|
||||
// arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
// 'heroTag': Utils.makeHeroTag(id),
|
||||
// 'videoType':
|
||||
// epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||
// },
|
||||
// );
|
||||
},
|
||||
onLongPress: () {
|
||||
if (onLongPress != null) {
|
||||
onLongPress!();
|
||||
} else {
|
||||
imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
cover: videoItem.pic,
|
||||
);
|
||||
}
|
||||
},
|
||||
if ([0, 16].contains(videoItem.attr).not) {
|
||||
Get.toNamed('/member?mid=${videoItem.owner.mid}');
|
||||
return;
|
||||
}
|
||||
onViewFav?.call();
|
||||
// Utils.toViewPage(
|
||||
// 'bvid=$bvid&cid=${videoItem.cid}${epId?.isNotEmpty == true ? '&epId=$epId' : ''}',
|
||||
// arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
// 'heroTag': Utils.makeHeroTag(id),
|
||||
// 'videoType':
|
||||
// epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||
// },
|
||||
// );
|
||||
},
|
||||
onLongPress: isSort == true
|
||||
? null
|
||||
: () {
|
||||
if (onLongPress != null) {
|
||||
onLongPress!();
|
||||
} else {
|
||||
imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
cover: videoItem.pic,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
|
||||
Reference in New Issue
Block a user