feat: sort fav

Closes #530

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-03-26 16:20:40 +08:00
parent aa378d924b
commit becb566ca8
5 changed files with 273 additions and 70 deletions

View 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(),
);
}
}

View File

@@ -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))

View File

@@ -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,