diff --git a/lib/common/widgets/appbar/appbar.dart b/lib/common/widgets/appbar/appbar.dart new file mode 100644 index 00000000..e50f532c --- /dev/null +++ b/lib/common/widgets/appbar/appbar.dart @@ -0,0 +1,91 @@ +import 'package:PiliPlus/pages/common/multi_select_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class AppBarWidget extends StatelessWidget implements PreferredSizeWidget { + const AppBarWidget({ + required this.child1, + required this.child2, + required this.visible, + super.key, + }); + + final PreferredSizeWidget child1; + final PreferredSizeWidget child2; + final bool visible; + @override + Size get preferredSize => child1.preferredSize; + + @override + Widget build(BuildContext context) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + transitionBuilder: (Widget child, Animation animation) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + child: !visible + ? KeyedSubtree.wrap(child1, 0) + : KeyedSubtree.wrap(child2, 1), + ); + } +} + +class MultiSelectAppBarWidget extends StatelessWidget + implements PreferredSizeWidget { + final MultiSelectMixin ctr; + final bool? visible; + final AppBar child; + final List? children; + + const MultiSelectAppBarWidget({ + super.key, + required this.ctr, + this.visible, + this.children, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return AppBarWidget( + visible: visible ?? ctr.enableMultiSelect.value, + child1: child, + child2: AppBar( + bottom: child.bottom, + leading: IconButton( + tooltip: '取消', + onPressed: ctr.handleSelect, + icon: const Icon(Icons.close_outlined), + ), + title: Obx(() => Text('已选: ${ctr.checkedCount}')), + actions: [ + TextButton( + style: TextButton.styleFrom( + visualDensity: VisualDensity.compact, + ), + onPressed: () => ctr.handleSelect(true), + child: const Text('全选'), + ), + ...?children, + TextButton( + style: TextButton.styleFrom( + visualDensity: VisualDensity.compact, + ), + onPressed: ctr.onConfirm, + child: Text( + '移除', + style: TextStyle(color: Get.theme.colorScheme.error), + ), + ), + const SizedBox(width: 6), + ], + ), + ); + } + + @override + Size get preferredSize => child.preferredSize; +} diff --git a/lib/http/fav.dart b/lib/http/fav.dart index 91524e75..388b6012 100644 --- a/lib/http/fav.dart +++ b/lib/http/fav.dart @@ -312,7 +312,7 @@ class FavHttp { static Future delNote({ required bool isPublish, - required List noteIds, + required Iterable noteIds, }) async { final res = await Request().post( isPublish ? Api.delPublishNote : Api.delNote, @@ -637,13 +637,13 @@ class FavHttp { } } - static Future copyOrMoveFav({ + static Future copyOrMoveFav({ required bool isCopy, required bool isFav, required dynamic srcMediaId, required dynamic tarMediaId, dynamic mid, - required List resources, + required Iterable resources, }) async { var res = await Request().post( isFav @@ -664,21 +664,21 @@ class FavHttp { options: Options(contentType: Headers.formUrlEncodedContentType), ); if (res.data['code'] == 0) { - return {'status': true}; + return const Success(null); } else { - return {'status': false, 'msg': res.data['message']}; + return Error(res.data['message']); } } - static Future allFavFolders(mid) async { + static Future> allFavFolders(Object mid) async { var res = await Request().get( Api.favFolder, queryParameters: {'up_mid': mid}, ); if (res.data['code'] == 0) { - return {'status': true, 'data': FavFolderData.fromJson(res.data['data'])}; + return Success(FavFolderData.fromJson(res.data['data'])); } else { - return {'status': false, 'msg': res.data['message']}; + return Error(res.data['message']); } } diff --git a/lib/http/user.dart b/lib/http/user.dart index eaf8d658..a7b56df7 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -151,7 +151,7 @@ class UserHttp { } // 移除已观看 - static Future toViewDel({required List aids}) async { + static Future toViewDel({required Iterable aids}) async { final Map params = { 'csrf': Accounts.main.csrf, 'resources': aids.join(','), @@ -204,7 +204,7 @@ class UserHttp { } // 删除历史记录 - static Future delHistory(List kidList) async { + static Future delHistory(Iterable kidList) async { var res = await Request().post( Api.delHistory, data: { diff --git a/lib/pages/common/multi_select_controller.dart b/lib/pages/common/multi_select_controller.dart index 04705675..1741190d 100644 --- a/lib/pages/common/multi_select_controller.dart +++ b/lib/pages/common/multi_select_controller.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:get/get.dart'; @@ -5,35 +6,60 @@ mixin MultiSelectData { bool? checked; } -abstract class MultiSelectController - extends CommonListController { +mixin MultiSelectMixin { late final RxBool enableMultiSelect = false.obs; - late final RxInt checkedCount = 0.obs; late final allSelected = false.obs; + int get checkedCount; + + void onSelect(T item, [bool disableSelect = true]); + void handleSelect([bool checked = false, bool disableSelect = true]); + void onConfirm(); +} + +abstract class MultiSelectController + extends CommonListController + with MultiSelectMixin, CommonMultiSelectMixin, DeleteItemMixin {} + +mixin CommonMultiSelectMixin on MultiSelectMixin { + Rx?>> get loadingState; + late final RxInt rxCount = 0.obs; + + @override + int get checkedCount => rxCount.value; + + Iterable get allChecked => + loadingState.value.data!.where((v) => v.checked == true); + + @override void onSelect(T item, [bool disableSelect = true]) { List list = loadingState.value.data!; item.checked = !(item.checked ?? false); - checkedCount.value = list.where((item) => item.checked == true).length; + if (item.checked!) { + rxCount.value++; + } else { + rxCount.value--; + } loadingState.refresh(); if (disableSelect) { - if (checkedCount.value == 0) { + if (checkedCount == 0) { enableMultiSelect.value = false; } } else { - allSelected.value = checkedCount.value == list.length; + allSelected.value = checkedCount == list.length; } } + @override void handleSelect([bool checked = false, bool disableSelect = true]) { if (loadingState.value.isSuccess) { - List? list = loadingState.value.data; + final list = loadingState.value.data; if (list?.isNotEmpty == true) { - for (T item in list!) { + for (var item in list!) { item.checked = checked; } loadingState.refresh(); - checkedCount.value = checked ? list.length : 0; + rxCount.value = checked ? list.length : 0; } } if (disableSelect && !checked) { @@ -41,3 +67,75 @@ abstract class MultiSelectController } } } + +mixin DeleteItemMixin + on CommonListController, CommonMultiSelectMixin { + Future afterDelete(Set result) async { + // TODO: result require hash + final remainList = loadingState.value.data!; + if (result.length == 1) { + remainList.remove(result.single); + } else { + remainList.removeWhere(result.contains); + } + if (remainList.isNotEmpty) { + loadingState.refresh(); + } else if (!isEnd) { + onReload(); + } + if (enableMultiSelect.value) { + rxCount.value = 0; + enableMultiSelect.value = false; + } + } +} + +// abstract class SetMultiSelectController +// extends CommonListController +// with MultiSelectMixin, SetCommonMultiSelectMixin {} + +// mixin SetCommonMultiSelectMixin on MultiSelectMixin { +// Rx?>> get loadingState; +// RxSet get selected; + +// @override +// int get checkedCount => selected.length; + +// R getId(T item); + +// @override +// void onSelect(T item, [bool disableSelect = true]) { +// final id = getId(item); +// if (selected.contains(id)) { +// selected.remove(id); +// } else { +// selected.add(id); +// } +// loadingState.refresh(); +// if (disableSelect) { +// if (checkedCount == 0) { +// enableMultiSelect.value = false; +// } +// } else { +// allSelected.value = checkedCount == loadingState.value.data!.length; +// } +// } + +// @override +// void handleSelect([bool checked = false, bool disableSelect = true]) { +// if (loadingState.value.isSuccess) { +// final list = loadingState.value.data; +// if (list?.isNotEmpty == true) { +// if (checked) { +// selected.addAll(list!.map(getId)); +// } else { +// selected.clear(); +// } +// loadingState.refresh(); +// } +// } +// if (disableSelect && !checked) { +// enableMultiSelect.value = false; +// } +// } +// } diff --git a/lib/pages/dynamics_create_vote/view.dart b/lib/pages/dynamics_create_vote/view.dart index c0c45691..b7e809c3 100644 --- a/lib/pages/dynamics_create_vote/view.dart +++ b/lib/pages/dynamics_create_vote/view.dart @@ -97,7 +97,7 @@ class _CreateVotePageState extends State { ..add( _buildInput( theme, - key: ValueKey(e.hashCode), + key: ObjectKey(e), showDel: showDel, onDel: () { FocusManager.instance.primaryFocus?.unfocus(); diff --git a/lib/pages/fav/note/child_view.dart b/lib/pages/fav/note/child_view.dart index 96c65fbd..b3aac48c 100644 --- a/lib/pages/fav/note/child_view.dart +++ b/lib/pages/fav/note/child_view.dart @@ -115,11 +115,11 @@ class _FavNoteChildPageState extends State tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: () { - if (_favNoteController.checkedCount.value != 0) { + if (_favNoteController.checkedCount != 0) { showConfirmDialog( context: context, title: '确定删除已选中的笔记吗?', - onConfirm: _favNoteController.onRemove, + onConfirm: _favNoteController.onConfirm, ); } }, diff --git a/lib/pages/fav/note/controller.dart b/lib/pages/fav/note/controller.dart index e8bb70f3..19996e02 100644 --- a/lib/pages/fav/note/controller.dart +++ b/lib/pages/fav/note/controller.dart @@ -34,24 +34,15 @@ class FavNoteController : FavHttp.noteList(page: page); } - Future onRemove() async { - List dataList = loadingState.value.data!; - Set removeList = dataList - .where((item) => item.checked == true) - .toSet(); + @override + Future onConfirm() async { + Set removeList = allChecked.toSet(); final res = await FavHttp.delNote( isPublish: isPublish, - noteIds: removeList - .map((item) => isPublish ? item.cvid : item.noteId) - .toList(), + noteIds: removeList.map((item) => isPublish ? item.cvid : item.noteId), ); if (res['status']) { - List remainList = dataList - .toSet() - .difference(removeList) - .toList(); - loadingState.value = Success(remainList); - enableMultiSelect.value = false; + afterDelete(removeList); SmartDialog.showToast('删除成功'); } else { SmartDialog.showToast(res['msg']); @@ -59,7 +50,7 @@ class FavNoteController } void onDisable() { - if (checkedCount.value != 0) { + if (checkedCount != 0) { handleSelect(); } enableMultiSelect.value = false; diff --git a/lib/pages/fav/pgc/child_view.dart b/lib/pages/fav/pgc/child_view.dart index 38be14ab..afb9c1c3 100644 --- a/lib/pages/fav/pgc/child_view.dart +++ b/lib/pages/fav/pgc/child_view.dart @@ -128,8 +128,7 @@ class _FavPgcChildPageState extends State child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { - if (_favPgcController.checkedCount.value != - 0) { + if (_favPgcController.checkedCount != 0) { _favPgcController.onUpdateList( item.followStatus, ); diff --git a/lib/pages/fav/pgc/controller.dart b/lib/pages/fav/pgc/controller.dart index f0f73846..83f5dba5 100644 --- a/lib/pages/fav/pgc/controller.dart +++ b/lib/pages/fav/pgc/controller.dart @@ -48,7 +48,7 @@ class FavPgcController ); void onDisable() { - if (checkedCount.value != 0) { + if (checkedCount != 0) { handleSelect(); } enableMultiSelect.value = false; @@ -65,22 +65,19 @@ class FavPgcController SmartDialog.showToast(result['msg']); } + @override + void onConfirm() { + assert(false, 'call onUpdateList'); + } + Future onUpdateList(int followStatus) async { - List dataList = loadingState.value.data!; - Set updateList = dataList - .where((item) => item.checked == true) - .toSet(); + final updateList = allChecked.toSet(); final res = await VideoHttp.pgcUpdate( seasonId: updateList.map((item) => item.seasonId).toList(), status: followStatus, ); if (res['status']) { - List remainList = dataList - .toSet() - .difference(updateList) - .toList(); - loadingState.value = Success(remainList); - enableMultiSelect.value = false; + afterDelete(updateList); try { final ctr = Get.find(tag: '$type$followStatus'); if (ctr.loadingState.value.isSuccess) { diff --git a/lib/pages/fav/pgc/widget/item.dart b/lib/pages/fav/pgc/widget/item.dart index 74ab889a..22f54ad6 100644 --- a/lib/pages/fav/pgc/widget/item.dart +++ b/lib/pages/fav/pgc/widget/item.dart @@ -18,7 +18,7 @@ class FavPgcItem extends StatelessWidget { }); final FavPgcItemModel item; - final MultiSelectController ctr; + final MultiSelectMixin ctr; final VoidCallback onSelect; final VoidCallback onUpdateStatus; diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index a63c1d23..0bee7a9b 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/fav_order_type.dart'; @@ -11,7 +12,6 @@ import 'package:PiliPlus/services/account_service.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/page_utils.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'; @@ -90,60 +90,27 @@ class FavDetailController order: order.value, ); - void onDelChecked(BuildContext context) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('确认删除所选收藏吗?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () async { - Get.back(); - List list = loadingState.value.data! - .where((e) => e.checked == true) - .toList(); - var result = await FavHttp.favVideo( - resources: list - .map((item) => '${item.id}:${item.type}') - .join(','), - delIds: mediaId.toString(), - ); - if (result['status']) { - List dataList = loadingState.value.data!; - List remainList = dataList - .toSet() - .difference(list.toSet()) - .toList(); - folderInfo - ..value.mediaCount -= list.length - ..refresh(); - if (remainList.isNotEmpty) { - loadingState.value = Success(remainList); - } else { - onReload(); - } - SmartDialog.showToast('取消收藏'); - checkedCount.value = 0; - enableMultiSelect.value = false; - } else { - SmartDialog.showToast(result['msg']); - } - }, - child: const Text('确认'), - ), - ], + @override + void onConfirm() { + showConfirmDialog( + context: Get.context!, + content: '确认删除所选收藏吗?', + title: '提示', + onConfirm: () async { + final checked = allChecked.toSet(); + var result = await FavHttp.favVideo( + resources: checked.map((item) => '${item.id}:${item.type}').join(','), + delIds: mediaId.toString(), ); + if (result['status']) { + afterDelete(checked); + folderInfo + ..value.mediaCount -= checked.length + ..refresh(); + SmartDialog.showToast('取消收藏'); + } else { + SmartDialog.showToast(result['msg']); + } }, ); } diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 6a5b2a13..507e0006 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -113,7 +113,7 @@ class _FavDetailPageState extends State { Obx( () { return Text( - '已选: ${_favDetailController.checkedCount.value}', + '已选: ${_favDetailController.checkedCount}', style: const TextStyle(fontSize: 15), ); }, @@ -327,7 +327,7 @@ class _FavDetailPageState extends State { style: TextButton.styleFrom( visualDensity: VisualDensity.compact, ), - onPressed: () => _favDetailController.onDelChecked(context), + onPressed: _favDetailController.onConfirm, child: Text( '删除', style: TextStyle(color: theme.colorScheme.error), diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index ada10aee..9e8c096e 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/models_new/history/data.dart'; @@ -26,6 +27,12 @@ class HistoryController int? max; int? viewAt; + @override + RxInt get rxCount => baseCtr.checkedCount; + + @override + RxBool get enableMultiSelect => baseCtr.enableMultiSelect; + @override void onInit() { super.onInit(); @@ -40,36 +47,6 @@ class HistoryController return super.onRefresh(); } - @override - void onSelect(HistoryItemModel item, [bool disableSelect = true]) { - List list = loadingState.value.data!; - item.checked = !(item.checked ?? false); - baseCtr.checkedCount.value = list - .where((item) => item.checked == true) - .length; - loadingState.refresh(); - if (baseCtr.checkedCount.value == 0) { - baseCtr.enableMultiSelect.value = false; - } - } - - @override - void handleSelect([bool checked = false, bool disableSelect = true]) { - if (loadingState.value.isSuccess) { - List? list = loadingState.value.data; - if (list?.isNotEmpty == true) { - for (HistoryItemModel item in list!) { - item.checked = checked; - } - baseCtr.checkedCount.value = checked ? list.length : 0; - loadingState.refresh(); - } - } - if (!checked) { - baseCtr.enableMultiSelect.value = false; - } - } - @override List? getDataList(HistoryData response) { return response.list; @@ -108,82 +85,43 @@ class HistoryController // 删除某条历史记录 void delHistory(HistoryItemModel item) { - _onDelete([item]); + _onDelete({item}); } // 删除已看历史记录 void onDelHistory() { if (loadingState.value.isSuccess) { - List list = loadingState.value.data! + final set = loadingState.value.data! .where((e) => e.progress == -1) - .toList(); - if (list.isNotEmpty) { - _onDelete(list); + .toSet(); + if (set.isNotEmpty) { + _onDelete(set); } else { SmartDialog.showToast('无已看记录'); } } } - Future _onDelete(List result) async { + Future _onDelete(Set result) async { SmartDialog.showLoading(msg: '请求中'); - List kidList = result.map((item) { - return '${item.history.business}_${item.kid}'; - }).toList(); - var response = await UserHttp.delHistory(kidList); + final response = await UserHttp.delHistory( + result.map((item) => '${item.history.business}_${item.kid}'), + ); if (response['status']) { - List remainList = loadingState.value.data! - .toSet() - .difference(result.toSet()) - .toList(); - if (remainList.isNotEmpty) { - loadingState.value = Success(remainList); - } else { - onReload(); - } - if (baseCtr.enableMultiSelect.value) { - baseCtr.checkedCount.value = 0; - baseCtr.enableMultiSelect.value = false; - } + afterDelete(result); } SmartDialog.dismiss(); SmartDialog.showToast(response['msg']); } // 删除选中的记录 - void onDelCheckedHistory(BuildContext context) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('确认删除所选历史记录吗?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - if (loadingState.value.isSuccess) { - _onDelete( - loadingState.value.data! - .where((e) => e.checked == true) - .toList(), - ); - } - }, - child: const Text('确认'), - ), - ], - ); - }, + @override + void onConfirm() { + showConfirmDialog( + context: Get.context!, + content: '确认删除所选历史记录吗?', + title: '提示', + onConfirm: () => _onDelete(allChecked.toSet()), ); } diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 63c0d8d6..78b7fdbb 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; @@ -65,9 +66,10 @@ class _HistoryPageState extends State } }, child: Scaffold( - appBar: AppBarWidget( + appBar: MultiSelectAppBarWidget( visible: enableMultiSelect, - child1: AppBar( + ctr: currCtr(), + child: AppBar( title: const Text('观看记录'), bottom: _buildPauseTip, actions: [ @@ -153,36 +155,6 @@ class _HistoryPageState extends State const SizedBox(width: 6), ], ), - child2: AppBar( - bottom: _buildPauseTip, - leading: IconButton( - tooltip: '取消', - onPressed: currCtr().handleSelect, - icon: const Icon(Icons.close_outlined), - ), - title: Obx( - () => Text( - '已选: ${_historyController.baseCtr.checkedCount.value}', - ), - ), - actions: [ - TextButton( - onPressed: () => currCtr().handleSelect(true), - child: const Text('全选'), - ), - TextButton( - onPressed: () => - currCtr().onDelCheckedHistory(context), - child: Text( - '删除', - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), - ), - ), - const SizedBox(width: 6), - ], - ), ), body: Obx( () => _historyController.tabs.isNotEmpty @@ -288,8 +260,7 @@ class _HistoryPageState extends State final item = response[index]; return HistoryItem( item: item, - ctr: _historyController.baseCtr, - onChoose: () => _historyController.onSelect(item), + ctr: _historyController, onDelete: (kid, business) => _historyController.delHistory(item), ); @@ -359,32 +330,3 @@ class _HistoryPageState extends State @override bool get wantKeepAlive => widget.type != null; } - -class AppBarWidget extends StatelessWidget implements PreferredSizeWidget { - const AppBarWidget({ - required this.child1, - required this.child2, - required this.visible, - super.key, - }); - - final PreferredSizeWidget child1; - final PreferredSizeWidget child2; - final bool visible; - @override - Size get preferredSize => child1.preferredSize; - - @override - Widget build(BuildContext context) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - transitionBuilder: (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, - child: child, - ); - }, - child: !visible ? child1 : child2, - ); - } -} diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index c75249ae..97b4b595 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -1,6 +1,5 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; -import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; import 'package:PiliPlus/http/search.dart'; @@ -9,7 +8,6 @@ import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/history_business_type.dart'; import 'package:PiliPlus/models_new/history/list.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; -import 'package:PiliPlus/pages/history/base_controller.dart'; import 'package:PiliPlus/utils/date_util.dart'; import 'package:PiliPlus/utils/duration_util.dart'; import 'package:PiliPlus/utils/feed_back.dart'; @@ -23,15 +21,13 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart class HistoryItem extends StatelessWidget { final HistoryItemModel item; - final dynamic ctr; - final Function? onChoose; - final Function(dynamic kid, dynamic business) onDelete; + final MultiSelectMixin ctr; + final void Function(int kid, String business) onDelete; const HistoryItem({ super.key, required this.item, - this.ctr, - this.onChoose, + required this.ctr, required this.onDelete, }); @@ -45,11 +41,9 @@ class HistoryItem extends StatelessWidget { type: MaterialType.transparency, child: InkWell( onTap: () async { - if (ctr is MultiSelectController || ctr is HistoryBaseController) { - if (ctr.enableMultiSelect.value) { - onChoose?.call(); - return; - } + if (ctr.enableMultiSelect.value) { + ctr.onSelect(item); + return; } if (item.history.business?.contains('article') == true) { PageUtils.toDupNamed( @@ -97,18 +91,16 @@ class HistoryItem extends StatelessWidget { } }, onLongPress: () { - if (ctr is MultiSelectController || ctr is HistoryBaseController) { - if (!ctr.enableMultiSelect.value) { - ctr.enableMultiSelect.value = true; - onChoose?.call(); - } - return; + if (!ctr.enableMultiSelect.value) { + ctr.enableMultiSelect.value = true; + ctr.onSelect(item); } - imageSaveDialog( - title: item.title, - cover: item.cover, - bvid: bvid, - ); + return; + // imageSaveDialog( + // title: item.title, + // cover: item.cover, + // bvid: bvid, + // ); }, child: Stack( clipBehavior: Clip.none, @@ -205,7 +197,7 @@ class HistoryItem extends StatelessWidget { ), onPressed: () { feedBack(); - onChoose?.call(); + ctr.onSelect(item); }, icon: Icon( Icons.done_all_outlined, @@ -287,7 +279,7 @@ class HistoryItem extends StatelessWidget { ), PopupMenuItem( onTap: () => - onDelete(item.kid, item.history.business), + onDelete(item.kid!, item.history.business!), height: 35, child: const Row( children: [ diff --git a/lib/pages/history_search/controller.dart b/lib/pages/history_search/controller.dart index 0eded101..4adfee29 100644 --- a/lib/pages/history_search/controller.dart +++ b/lib/pages/history_search/controller.dart @@ -1,12 +1,19 @@ +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/models_new/history/data.dart'; import 'package:PiliPlus/models_new/history/list.dart'; import 'package:PiliPlus/pages/common/common_search_controller.dart'; +import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; class HistorySearchController - extends CommonSearchController { + extends CommonSearchController + with + MultiSelectMixin, + CommonMultiSelectMixin, + DeleteItemMixin { @override Future> customGetData() => UserHttp.searchHistory( pn: page, @@ -18,12 +25,14 @@ class HistorySearchController return response.list; } - Future onDelHistory(int index, kid, business) async { - String resKid = 'archive_$kid'; + Future onDelHistory(int index, kid, String business) async { + final String resKid; if (business == 'live') { resKid = 'live_$kid'; } else if (business.contains('article')) { resKid = 'article_$kid'; + } else { + resKid = 'archive_$kid'; } var res = await UserHttp.delHistory([resKid]); @@ -34,4 +43,26 @@ class HistorySearchController } SmartDialog.showToast(res['msg']); } + + @override + void onConfirm() { + showConfirmDialog( + context: Get.context!, + content: '确认删除所选历史记录吗?', + title: '提示', + onConfirm: () async { + SmartDialog.showLoading(msg: '请求中'); + final result = allChecked.toSet(); + final kidList = result.map( + (item) => '${item.history.business!}_${item.kid!}', + ); + var response = await UserHttp.delHistory(kidList); + if (response['status']) { + afterDelete(result); + } + SmartDialog.dismiss(); + SmartDialog.showToast(response['msg']); + }, + ); + } } diff --git a/lib/pages/history_search/view.dart b/lib/pages/history_search/view.dart index bf74a20d..7a5032dd 100644 --- a/lib/pages/history_search/view.dart +++ b/lib/pages/history_search/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/models_new/history/data.dart'; import 'package:PiliPlus/models_new/history/list.dart'; import 'package:PiliPlus/pages/common/common_search_page.dart'; @@ -28,6 +29,31 @@ class _HistorySearchPageState tag: Utils.generateRandomString(8), ); + @override + Widget build(BuildContext context) { + // TODO: refa + return Obx(() { + final parent = super.build(context) as Scaffold; + final enableMultiSelect = controller.enableMultiSelect.value; + return PopScope( + canPop: !enableMultiSelect, + onPopInvokedWithResult: (didPop, result) { + if (enableMultiSelect) { + controller.handleSelect(); + } + }, + child: Scaffold( + resizeToAvoidBottomInset: parent.resizeToAvoidBottomInset, + appBar: MultiSelectAppBarWidget( + ctr: controller, + child: parent.appBar as AppBar, + ), + body: parent.body, + ), + ); + }); + } + @override Widget buildList(List list) { return SliverGrid( @@ -42,10 +68,8 @@ class _HistorySearchPageState return HistoryItem( item: item, ctr: controller, - onChoose: null, - onDelete: (kid, business) { - controller.onDelHistory(index, kid, business); - }, + onDelete: (kid, business) => + controller.onDelHistory(index, kid, business), ); }, ), diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index ac0c36b2..7dc6b8b7 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -27,6 +27,12 @@ class LaterController extends MultiSelectController { final LaterBaseController baseCtr = Get.put(LaterBaseController()); + @override + RxBool get enableMultiSelect => baseCtr.enableMultiSelect; + + @override + RxInt get rxCount => baseCtr.checkedCount; + @override Future> customGetData() => UserHttp.seeYouLater( page: page, @@ -34,36 +40,6 @@ class LaterController extends MultiSelectController { asc: asc.value, ); - @override - void onSelect(LaterItemModel item, [bool disableSelect = true]) { - List list = loadingState.value.data!; - item.checked = !(item.checked ?? false); - baseCtr.checkedCount.value = list - .where((item) => item.checked == true) - .length; - loadingState.refresh(); - if (baseCtr.checkedCount.value == 0) { - baseCtr.enableMultiSelect.value = false; - } - } - - @override - void handleSelect([bool checked = false, bool disableSelect = true]) { - if (loadingState.value.isSuccess) { - List? list = loadingState.value.data; - if (list?.isNotEmpty == true) { - for (LaterItemModel item in list!) { - item.checked = checked; - } - baseCtr.checkedCount.value = checked ? list.length : 0; - loadingState.refresh(); - } - } - if (!checked) { - baseCtr.enableMultiSelect.value = false; - } - } - @override void onInit() { super.onInit(); @@ -102,7 +78,7 @@ class LaterController extends MultiSelectController { TextButton( onPressed: () async { Get.back(); - var res = await UserHttp.toViewDel(aids: [aid]); + final res = await UserHttp.toViewDel(aids: {aid!}); if (res['status']) { baseCtr.counts[laterViewType] = baseCtr.counts[laterViewType]! - 1; @@ -148,51 +124,24 @@ class LaterController extends MultiSelectController { ); } - void onDelChecked(BuildContext context) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('提示'), - content: const Text('确认删除所选稍后再看吗?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - _onDelete( - loadingState.value.data! - .where((e) => e.checked == true) - .toList(), - ); - }, - child: const Text('确认'), - ), - ], - ); - }, + @override + void onConfirm() { + showConfirmDialog( + context: Get.context!, + content: '确认删除所选稍后再看吗?', + title: '提示', + onConfirm: () => _onDelete(allChecked.toSet()), ); } - Future _onDelete(List result) async { + Future _onDelete(Set result) async { SmartDialog.showLoading(msg: '请求中'); - List aids = result.map((item) => item.aid).toList(); - var res = await UserHttp.toViewDel(aids: aids); + final res = await UserHttp.toViewDel(aids: result.map((item) => item.aid!)); if (res['status']) { - Set remainList = loadingState.value.data! - .toSet() - .difference(result.toSet()); + afterDelete(result); + baseCtr.counts[laterViewType] = - baseCtr.counts[laterViewType]! - aids.length; - loadingState.value = Success(remainList.toList()); + baseCtr.counts[laterViewType]! - result.length; if (baseCtr.enableMultiSelect.value) { baseCtr.checkedCount.value = 0; baseCtr.enableMultiSelect.value = false; diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index 7cb24653..ba8c3be0 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -1,8 +1,8 @@ +import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/models/common/later_view_type.dart'; import 'package:PiliPlus/models_new/later/data.dart'; import 'package:PiliPlus/models_new/later/list.dart'; -import 'package:PiliPlus/pages/history/view.dart' show AppBarWidget; import 'package:PiliPlus/pages/later/base_controller.dart'; import 'package:PiliPlus/pages/later/controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; @@ -128,9 +128,54 @@ class _LaterPageState extends State final theme = Theme.of(context); Color color = theme.colorScheme.secondary; - return AppBarWidget( + return MultiSelectAppBarWidget( visible: enableMultiSelect, - child1: AppBar( + ctr: currCtr(), + children: [ + TextButton( + style: TextButton.styleFrom( + visualDensity: VisualDensity.compact, + ), + onPressed: () { + final ctr = currCtr(); + RequestUtils.onCopyOrMove( + context: context, + isCopy: true, + ctr: ctr, + mediaId: null, + mid: ctr.accountService.mid, + ); + }, + child: Text( + '复制', + style: TextStyle( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + TextButton( + style: TextButton.styleFrom( + visualDensity: VisualDensity.compact, + ), + onPressed: () { + final ctr = currCtr(); + RequestUtils.onCopyOrMove( + context: context, + isCopy: false, + ctr: ctr, + mediaId: null, + mid: ctr.accountService.mid, + ); + }, + child: Text( + '移动', + style: TextStyle( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + ], + child: AppBar( title: const Text('稍后再看'), actions: [ IconButton( @@ -251,76 +296,6 @@ class _LaterPageState extends State const SizedBox(width: 8), ], ), - child2: AppBar( - leading: IconButton( - tooltip: '取消', - onPressed: currCtr().handleSelect, - icon: const Icon(Icons.close_outlined), - ), - title: Obx(() => Text('已选: ${_baseCtr.checkedCount.value}')), - actions: [ - TextButton( - style: TextButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - onPressed: () => currCtr().handleSelect(true), - child: const Text('全选'), - ), - TextButton( - style: TextButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - onPressed: () { - final ctr = currCtr(); - RequestUtils.onCopyOrMove( - context: context, - isCopy: true, - ctr: ctr, - mediaId: null, - mid: ctr.accountService.mid, - ); - }, - child: Text( - '复制', - style: TextStyle( - color: theme.colorScheme.onSurfaceVariant, - ), - ), - ), - TextButton( - style: TextButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - onPressed: () { - final ctr = currCtr(); - RequestUtils.onCopyOrMove( - context: context, - isCopy: false, - ctr: ctr, - mediaId: null, - mid: ctr.accountService.mid, - ); - }, - child: Text( - '移动', - style: TextStyle( - color: theme.colorScheme.onSurfaceVariant, - ), - ), - ), - TextButton( - style: TextButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - onPressed: () => currCtr().onDelChecked(context), - child: Text( - '移除', - style: TextStyle(color: theme.colorScheme.error), - ), - ), - const SizedBox(width: 6), - ], - ), ); } } diff --git a/lib/pages/later_search/controller.dart b/lib/pages/later_search/controller.dart index 0160339a..2cdfd0bb 100644 --- a/lib/pages/later_search/controller.dart +++ b/lib/pages/later_search/controller.dart @@ -23,7 +23,7 @@ class LaterSearchController return response.list; } - Future toViewDel(BuildContext context, int index, aid) async { + Future toViewDel(BuildContext context, int index, int aid) async { var res = await UserHttp.toViewDel(aids: [aid]); if (res['status']) { loadingState.value.data!.removeAt(index); @@ -31,4 +31,25 @@ class LaterSearchController } SmartDialog.showToast(res['msg']); } + + // @override + // void onConfirm() { + // showConfirmDialog( + // context: Get.context!, + // content: '确认删除所选稍后再看吗?', + // title: '提示', + // onConfirm: () async { + // final result = allChecked.toSet(); + // SmartDialog.showLoading(msg: '请求中'); + // var res = await UserHttp.toViewDel( + // aids: result.map((item) => item.aid!), + // ); + // if (res['status']) { + // afterDelete(result); + // } + // SmartDialog.dismiss(); + // SmartDialog.showToast(res['msg']); + // }, + // ); + // } } diff --git a/lib/pages/later_search/view.dart b/lib/pages/later_search/view.dart index 865d6da6..8f3edd20 100644 --- a/lib/pages/later_search/view.dart +++ b/lib/pages/later_search/view.dart @@ -27,6 +27,31 @@ class _LaterSearchPageState tag: Utils.generateRandomString(8), ); + // @override + // Widget build(BuildContext context) { + // // TODO: refa + // return Obx(() { + // final parent = super.build(context) as Scaffold; + // final enableMultiSelect = controller.enableMultiSelect.value; + // return PopScope( + // canPop: !enableMultiSelect, + // onPopInvokedWithResult: (didPop, result) { + // if (enableMultiSelect) { + // controller.handleSelect(); + // } + // }, + // child: Scaffold( + // resizeToAvoidBottomInset: parent.resizeToAvoidBottomInset, + // appBar: MultiSelectAppBarWidget( + // ctr: controller, + // child: parent.appBar as AppBar, + // ), + // body: parent.body, + // ), + // ); + // }); + // } + @override Widget buildList(List list) { return SliverGrid( @@ -73,7 +98,7 @@ class _LaterSearchPageState onConfirm: () => controller.toViewDel( context, index, - item.aid, + item.aid!, ), ), icon: Icons.clear, diff --git a/lib/pages/setting/models/extra_settings.dart b/lib/pages/setting/models/extra_settings.dart index 903475bb..eab2544c 100644 --- a/lib/pages/setting/models/extra_settings.dart +++ b/lib/pages/setting/models/extra_settings.dart @@ -14,7 +14,6 @@ import 'package:PiliPlus/models/common/reply/reply_sort_type.dart'; import 'package:PiliPlus/models/common/settings_type.dart'; import 'package:PiliPlus/models/common/super_resolution_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/models_new/fav/fav_folder/data.dart'; import 'package:PiliPlus/pages/common/common_slide_page.dart'; import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/hot/controller.dart'; @@ -786,9 +785,8 @@ List get extraSettings => [ onTap: () async { if (Accounts.main.isLogin) { final res = await FavHttp.allFavFolders(Accounts.main.mid); - if (res['status']) { - final FavFolderData data = res['data']; - final list = data.list; + if (res.isSuccess) { + final list = res.data.list; if (list.isNullOrEmpty) { return; } @@ -821,7 +819,7 @@ List get extraSettings => [ ), ); } else { - SmartDialog.showToast('${res['msg']}'); + res.toast(); } } }, diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart index 0f0d1c46..4a3e84f9 100644 --- a/lib/utils/request_utils.dart +++ b/lib/utils/request_utils.dart @@ -18,7 +18,6 @@ import 'package:PiliPlus/http/validate.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/login/model.dart'; -import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; import 'package:PiliPlus/pages/common/multi_select_controller.dart'; import 'package:PiliPlus/pages/dynamics_tab/controller.dart'; import 'package:PiliPlus/pages/group_panel/view.dart'; @@ -376,11 +375,9 @@ class RequestUtils { required dynamic mid, }) { FavHttp.allFavFolders(mid).then((res) { - if (context.mounted && - res['status'] && - (res['data'].list as List?)?.isNotEmpty == true) { - List list = res['data'].list; - dynamic checkedId; + if (context.mounted && res.dataOrNull?.list?.isNotEmpty == true) { + final list = res.data.list!; + int? checkedId; showDialog( context: context, builder: (context) { @@ -392,7 +389,7 @@ class RequestUtils { builder: (context) => Column( children: List.generate(list.length, (index) { final item = list[index]; - return RadioWidget( + return RadioWidget( padding: const EdgeInsets.only(left: 14), title: item.title, groupValue: checkedId, @@ -421,9 +418,7 @@ class RequestUtils { TextButton( onPressed: () { if (checkedId != null) { - List resources = ctr.loadingState.value.data! - .where((e) => e.checked == true) - .toList(); + Set resources = ctr.allChecked.toSet(); SmartDialog.showLoading(); FavHttp.copyOrMoveFav( isCopy: isCopy, @@ -439,22 +434,20 @@ class RequestUtils { .toList(), mid: isCopy ? mid : null, ).then((res) { - if (res['status']) { + if (res.isSuccess) { ctr.handleSelect(false); if (!isCopy) { - List dataList = ctr.loadingState.value.data!; - List remainList = dataList - .toSet() - .difference(resources.toSet()) - .toList(); - ctr.loadingState.value = Success(remainList); + ctr.loadingState.value.data!.removeWhere( + resources.contains, + ); + ctr.loadingState.refresh(); } SmartDialog.dismiss(); SmartDialog.showToast('${isCopy ? '复制' : '移动'}成功'); Get.back(); } else { SmartDialog.dismiss(); - SmartDialog.showToast('${res['msg']}'); + res.toast(); } }); } @@ -466,7 +459,7 @@ class RequestUtils { }, ); } else { - SmartDialog.showToast('${res['msg']}'); + res.toast(); } }); }