From 80ecd35784e179f86ff5058b956aebbd6b1d2ddc Mon Sep 17 00:00:00 2001 From: dom Date: Mon, 27 Apr 2026 20:53:55 +0800 Subject: [PATCH] sort follow tag Signed-off-by: dom --- lib/http/api.dart | 2 + lib/http/follow.dart | 24 +++++ lib/pages/fav_folder_sort/view.dart | 4 +- lib/pages/fav_sort/view.dart | 25 ++--- lib/pages/follow/child/child_view.dart | 27 +++++- lib/pages/follow/view.dart | 21 ++-- lib/pages/follow_tag_sort/view.dart | 128 +++++++++++++++++++++++++ lib/utils/utils.dart | 4 + 8 files changed, 214 insertions(+), 21 deletions(-) create mode 100644 lib/pages/follow_tag_sort/view.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 7b6f64c42..bea77e0ac 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -1006,4 +1006,6 @@ abstract final class Api { '${HttpString.liveBaseUrl}/xlive/app-ucenter/v1/guard/MainGuardCardAll'; static const String bubble = '/x/tribee/v1/dyn/all'; + + static const String sortFollowTag = '/x/relation/tags/update_sort'; } diff --git a/lib/http/follow.dart b/lib/http/follow.dart index d3ed1c715..bd19e2fc4 100644 --- a/lib/http/follow.dart +++ b/lib/http/follow.dart @@ -3,6 +3,8 @@ import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/follow/data.dart'; +import 'package:PiliPlus/utils/accounts.dart'; +import 'package:dio/dio.dart' show Options, Headers; abstract final class FollowHttp { static Future> followings({ @@ -27,4 +29,26 @@ abstract final class FollowHttp { return Error(errorMsg[res.data['code']] ?? res.data['message']); } } + + static Future> sortFollowTag({ + required String tagids, + }) async { + final res = await Request().post( + Api.sortFollowTag, + queryParameters: { + 'x-bili-device-req-json': + '{"platform":"web","device":"pc","spmid":"333.1387"}', + }, + data: { + 'tagids': tagids, + 'csrf': Accounts.main.csrf, + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return const Success(null); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/pages/fav_folder_sort/view.dart b/lib/pages/fav_folder_sort/view.dart index 093d4f44c..d2f787c13 100644 --- a/lib/pages/fav_folder_sort/view.dart +++ b/lib/pages/fav_folder_sort/view.dart @@ -39,7 +39,9 @@ class _FavFolderSortPageState extends State { if (res.isSuccess) { SmartDialog.showToast('排序完成'); _favController.loadingState.value = Success(sortList); - Get.back(); + if (mounted) { + Get.back(); + } } else { res.toast(); } diff --git a/lib/pages/fav_sort/view.dart b/lib/pages/fav_sort/view.dart index cc61cddf7..3ce744c79 100644 --- a/lib/pages/fav_sort/view.dart +++ b/lib/pages/fav_sort/view.dart @@ -51,22 +51,25 @@ class _FavSortPageState extends State { title: Text('排序: ${_favDetailController.folderInfo.value.title}'), actions: [ TextButton( - onPressed: () async { + onPressed: () { if (sort.isEmpty) { Get.back(); return; } - final res = await FavHttp.sortFav( + FavHttp.sortFav( mediaId: _favDetailController.mediaId, sort: sort.join(','), - ); - if (res.isSuccess) { - SmartDialog.showToast('排序完成'); - _favDetailController.loadingState.value = Success(sortList); - Get.back(); - } else { - res.toast(); - } + ).then((res) { + if (res.isSuccess) { + SmartDialog.showToast('排序完成'); + _favDetailController.loadingState.value = Success(sortList); + if (mounted) { + Get.back(); + } + } else { + res.toast(); + } + }); }, child: const Text('完成'), ), @@ -108,7 +111,7 @@ class _FavSortPageState extends State { itemBuilder: (context, index) { final item = sortList[index]; return SizedBox( - key: Key(item.id.toString()), + key: ValueKey(item.id), height: 98, child: FavVideoCardH(item: item), ); diff --git a/lib/pages/follow/child/child_view.dart b/lib/pages/follow/child/child_view.dart index 649bb6d8d..775e1cd62 100644 --- a/lib/pages/follow/child/child_view.dart +++ b/lib/pages/follow/child/child_view.dart @@ -38,17 +38,40 @@ class FollowChildPage extends StatefulWidget { class _FollowChildPageState extends State with AutomaticKeepAliveClientMixin { - late final FollowChildController _followController; + late String _tag; + late FollowChildController _followController; + + String get _newTag => + '${widget.tag ?? Utils.generateRandomString(8)}${widget.tagid}'; @override void initState() { super.initState(); + _initController(); + } + + void _initController() { + _tag = _newTag; _followController = Get.put( FollowChildController(widget.controller, widget.mid, widget.tagid), - tag: '${widget.tag ?? Utils.generateRandomString(8)}${widget.tagid}', + tag: _tag, ); } + @override + void didUpdateWidget(FollowChildPage oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.tagid != widget.tagid) { + final newTag = _newTag; + if (Get.isRegistered(tag: newTag)) { + _followController = Get.find(tag: newTag); + } else { + Get.delete(tag: _tag); + _initController(); + } + } + } + @override Widget build(BuildContext context) { super.build(context); diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index b7c4ff957..094db74c5 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/models/member/tags.dart'; import 'package:PiliPlus/pages/follow/child/child_controller.dart'; import 'package:PiliPlus/pages/follow/child/child_view.dart'; import 'package:PiliPlus/pages/follow/controller.dart'; +import 'package:PiliPlus/pages/follow_tag_sort/view.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -71,6 +72,16 @@ class _FollowPageState extends State { icon: const Icon(Icons.add), tooltip: '新建分组', ), + IconButton( + onPressed: () { + if (_followController.followState.value is! Success) { + return; + } + Get.to(FollowTagSortPage(controller: _followController)); + }, + icon: const Icon(Icons.sort), + tooltip: '分组排序', + ), IconButton( onPressed: () => Get.toNamed( '/followSearch', @@ -87,10 +98,10 @@ class _FollowPageState extends State { PopupMenuItem( onTap: () => Get.toNamed('/blackListPage'), child: const Row( - mainAxisSize: MainAxisSize.min, + spacing: 10, + mainAxisSize: .min, children: [ Icon(Icons.block, size: 19), - SizedBox(width: 10), Text('黑名单管理'), ], ), @@ -109,10 +120,6 @@ class _FollowPageState extends State { tagid: item?.tagid, ); - bool _isCustomTag(int? tagid) { - return tagid != null && tagid != 0 && tagid != -10 && tagid != -2; - } - Widget _buildBody(LoadingState loadingState) { return switch (loadingState) { Loading() => m3eLoading, @@ -128,7 +135,7 @@ class _FollowPageState extends State { return Obx(() { final item = _followController.tabs[index]; int? count = item.count; - if (_isCustomTag(item.tagid)) { + if (Utils.isCustomFollowTag(item.tagid)) { return GestureDetector( behavior: HitTestBehavior.translucent, onLongPress: () { diff --git a/lib/pages/follow_tag_sort/view.dart b/lib/pages/follow_tag_sort/view.dart new file mode 100644 index 000000000..8a988647d --- /dev/null +++ b/lib/pages/follow_tag_sort/view.dart @@ -0,0 +1,128 @@ +import 'package:PiliPlus/http/follow.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/member/tags.dart'; +import 'package:PiliPlus/pages/follow/controller.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +class FollowTagSortPage extends StatefulWidget { + const FollowTagSortPage({super.key, required this.controller}); + + final FollowController controller; + + @override + State createState() => _FollowTagSortPageState(); +} + +class _FollowTagSortPageState extends State { + late ColorScheme _scheme; + final GlobalKey _key = GlobalKey(); + final List _defTags = []; + final List _customTags = []; + + @override + void initState() { + super.initState(); + for (final e in widget.controller.tabs) { + if (Utils.isCustomFollowTag(e.tagid)) { + _customTags.add(e); + } else { + _defTags.add(e); + } + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _scheme = ColorScheme.of(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('关注分组排序'), + actions: _customTags.isNotEmpty + ? [ + TextButton( + onPressed: () async { + final res = await FollowHttp.sortFollowTag( + tagids: _customTags.map((e) => e.tagid).join(','), + ); + if (res.isSuccess) { + SmartDialog.showToast('排序完成'); + final tabs = _defTags + _customTags; + widget.controller + ..tabs.value = tabs + ..onInitTab() + ..followState.value = Success(tabs.hashCode); + if (mounted) { + Get.back(); + } + } else { + res.toast(); + } + }, + child: const Text('完成'), + ), + const SizedBox(width: 16), + ] + : null, + ), + body: _buildBody, + ); + } + + void onReorder(int oldIndex, int newIndex) { + if (newIndex > oldIndex) { + newIndex -= 1; + } + + final tabsItem = _customTags.removeAt(oldIndex); + _customTags.insert(newIndex, tabsItem); + + setState(() {}); + } + + Widget get _buildBody { + return ReorderableListView.builder( + key: _key, + onReorder: onReorder, + physics: const AlwaysScrollableScrollPhysics(), + padding: + MediaQuery.viewPaddingOf(context).copyWith(top: 0) + + const EdgeInsets.only(bottom: 100), + header: Column( + children: _defTags.map((e) => _buildItem(e, enabled: false)).toList(), + ), + itemCount: _customTags.length, + itemBuilder: (context, index) { + return _buildItem(_customTags[index]); + }, + ); + } + + Widget _buildItem( + MemberTagItemModel item, { + bool enabled = true, + }) { + return ListTile( + textColor: enabled ? null : _scheme.outline, + key: ValueKey(item.tagid), + leading: enabled + ? const Icon(Icons.group_outlined) + : Icon( + size: 23, + Icons.lock_outline, + color: _scheme.outline, + ), + minLeadingWidth: 0, + title: Text('${item.name} (${item.count})'), + subtitle: item.tip?.isNotEmpty == true ? Text(item.tip!) : null, + ); + } +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 6e15a5b4a..496882762 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -21,6 +21,10 @@ abstract final class Utils { static const jsonEncoder = JsonEncoder.withIndent(' '); + static bool isCustomFollowTag(int? tagid) { + return tagid != null && tagid != 0 && tagid != -10 && tagid != -2; + } + static String levelName( Object level, { bool isSeniorMember = false,