From 49b631d560b4ce90c2c1f5241f73f97e5baa3da0 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Mon, 27 Jan 2025 14:43:17 +0800 Subject: [PATCH] feat: copy/move fav Signed-off-by: bggRGjQaUbCoE --- lib/common/widgets/radio_widget.dart | 25 +++++ lib/http/api.dart | 6 +- lib/http/video.dart | 41 +++++++- lib/pages/dynamics/widgets/author_panel.dart | 2 +- lib/pages/fav_detail/controller.dart | 7 +- lib/pages/fav_detail/view.dart | 46 +++++++++ lib/pages/member/view.dart | 25 +---- lib/utils/utils.dart | 101 +++++++++++++++++++ 8 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 lib/common/widgets/radio_widget.dart diff --git a/lib/common/widgets/radio_widget.dart b/lib/common/widgets/radio_widget.dart new file mode 100644 index 00000000..98143081 --- /dev/null +++ b/lib/common/widgets/radio_widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +Widget radioWidget({ + required T value, + T? groupValue, + required ValueChanged onChanged, + required String title, + double? paddingStart, +}) { + return InkWell( + onTap: () => onChanged(value), + child: Row( + children: [ + if (paddingStart != null) SizedBox(width: paddingStart), + Radio( + value: value, + groupValue: groupValue, + onChanged: onChanged, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + Text(title), + ], + ), + ); +} diff --git a/lib/http/api.dart b/lib/http/api.dart index 0aa05e51..5b544f37 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -121,7 +121,11 @@ class Api { // up_mid num 目标用户mid 必要 // type num 目标内容属性 非必要 默认为全部 0:全部 2:视频稿件 // rid num 目标 视频稿件avid - static const String videoInFolder = '/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 relatedList = '/x/web-interface/archive/related'; diff --git a/lib/http/video.dart b/lib/http/video.dart index 1487e88b..35f47fbb 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -676,6 +676,45 @@ class VideoHttp { // } // } + static Future copyOrMoveFav({ + required bool isCopy, + required dynamic srcMediaId, + required dynamic tarMediaId, + dynamic mid, + required List resources, + }) async { + var res = await Request().post( + isCopy ? Api.copyFav : Api.moveFav, + data: { + if (srcMediaId != null) 'src_media_id': srcMediaId, + 'tar_media_id': tarMediaId, + if (mid != null) 'mid': mid, + 'resources': resources.join(','), + 'platform': 'web', + 'csrf': await Request.getCsrf(), + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return {'status': true}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + + static Future allFavFolders(mid) async { + var res = await Request().get( + Api.favFolder, + queryParameters: {'up_mid': mid}, + ); + if (res.data['code'] == 0) { + FavFolderData data = FavFolderData.fromJson(res.data['data']); + return {'status': true, 'data': data}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + // 查看视频被收藏在哪个文件夹 static Future videoInFolder({ dynamic mid, @@ -683,7 +722,7 @@ class VideoHttp { dynamic type, }) async { var res = await Request().get( - Api.videoInFolder, + Api.favFolder, queryParameters: { 'up_mid': mid, 'rid': rid, diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 0d6be4c6..024bad6f 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -1,6 +1,6 @@ +import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/http/index.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/pages/member/view.dart' show radioWidget; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:dio/dio.dart'; diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index f59fd9c2..8fdbf226 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -17,6 +17,8 @@ class FavDetailController extends MultiSelectController { RxBool isOwner = false.obs; RxBool titleCtr = false.obs; + dynamic mid; + @override void onInit() { // item = Get.arguments; @@ -26,6 +28,8 @@ class FavDetailController extends MultiSelectController { } super.onInit(); + mid = GStorage.userInfo.get('userInfoCache')?.mid; + queryData(); } @@ -33,8 +37,7 @@ class FavDetailController extends MultiSelectController { bool customHandleResponse(Success response) { if (currentPage == 1) { item.value = response.response.info; - isOwner.value = response.response.info.mid == - GStorage.userInfo.get('userInfoCache')?.mid; + isOwner.value = response.response.info.mid == mid; } if (response.response.medias.isEmpty) { isEnd = true; diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 961f15dc..5b7049d2 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -111,11 +111,57 @@ class _FavDetailPageState extends State { actions: _favDetailController.enableMultiSelect.value ? [ TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), onPressed: () => _favDetailController.handleSelect(true), child: const Text('全选'), ), TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), + onPressed: () => Utils.onCopyOrMove( + context: context, + isCopy: true, + ctr: _favDetailController, + ), + child: Text( + '复制到', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ), + TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), + onPressed: () => Utils.onCopyOrMove( + context: context, + isCopy: false, + ctr: _favDetailController, + ), + child: Text( + '移动到', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ), + TextButton( + style: TextButton.styleFrom( + visualDensity: + VisualDensity(horizontal: -2, vertical: -2), + ), onPressed: () => _favDetailController.onDelChecked(context), child: Text( diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 59fc3c28..877308f5 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -649,27 +650,3 @@ Widget _checkBoxWidget( ), ); } - -Widget radioWidget({ - required T value, - T? groupValue, - required ValueChanged onChanged, - required String title, - double? paddingStart, -}) { - return InkWell( - onTap: () => onChanged(value), - child: Row( - children: [ - if (paddingStart != null) SizedBox(width: paddingStart), - Radio( - value: value, - groupValue: groupValue, - onChanged: onChanged, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - Text(title), - ], - ), - ); -} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 0bc7e403..d9351f7a 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -4,10 +4,12 @@ import 'dart:io'; import 'dart:math'; import 'package:PiliPlus/build_config.dart'; import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'; +import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/video.dart'; @@ -15,6 +17,7 @@ import 'package:PiliPlus/models/bangumi/info.dart'; import 'package:PiliPlus/models/common/search_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/live/item.dart'; +import 'package:PiliPlus/models/user/fav_folder.dart'; import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.dart'; import 'package:PiliPlus/pages/video/detail/introduction/widgets/group_panel.dart'; import 'package:PiliPlus/utils/extension.dart'; @@ -39,6 +42,104 @@ class Utils { static const channel = MethodChannel("PiliPlus"); + static void onCopyOrMove({ + required BuildContext context, + required bool isCopy, + required dynamic ctr, + dynamic mediaId, + }) { + VideoHttp.allFavFolders(ctr.mid).then((res) { + if (context.mounted && + res['status'] && + (res['data'].list as List?)?.isNotEmpty == true) { + List list = res['data'].list; + dynamic checkedId; + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('${isCopy ? '复制' : '移动'}到'), + contentPadding: const EdgeInsets.only(top: 5), + content: SingleChildScrollView( + child: Builder( + builder: (context) => Column( + children: List.generate(list.length, (index) { + return radioWidget( + paddingStart: 14, + title: list[index].title ?? '', + groupValue: checkedId, + value: list[index].id, + onChanged: (value) { + checkedId = value; + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + }, + ); + }), + ), + ), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () { + if (checkedId != null) { + List resources = + ((ctr.loadingState.value as Success).response as List) + .where((e) => e.checked == true) + .toList(); + SmartDialog.showLoading(); + VideoHttp.copyOrMoveFav( + isCopy: isCopy, + srcMediaId: mediaId, + tarMediaId: checkedId, + resources: resources + .map((item) => '${item.id}:${item.type}') + .toList(), + mid: isCopy ? ctr.mid : null, + ).then((res) { + if (res['status']) { + ctr.handleSelect(false); + if (isCopy.not) { + List dataList = + (ctr.loadingState.value as Success).response; + List remainList = dataList + .toSet() + .difference(resources.toSet()) + .toList(); + ctr.loadingState.value = + LoadingState.success(remainList); + } + SmartDialog.dismiss(); + SmartDialog.showToast('${isCopy ? '复制' : '移动'}成功'); + Get.back(); + } else { + SmartDialog.dismiss(); + SmartDialog.showToast('${res['msg']}'); + } + }); + } + }, + child: Text('确认'), + ), + ], + ); + }, + ); + } else { + SmartDialog.showToast('${res['msg']}'); + } + }); + } + static void showFavBottomSheet({ required BuildContext context, required dynamic ctr,