From 2ef3a8cd253e74638fd37f486c1dd4a4a03b509a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 1 Oct 2023 10:35:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=A4=9A=E9=80=89=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/user/history.dart | 3 + lib/pages/history/controller.dart | 47 ++++++- lib/pages/history/view.dart | 198 +++++++++++++++++++++------- lib/pages/history/widgets/item.dart | 162 ++++++++++++++++------- 4 files changed, 321 insertions(+), 89 deletions(-) diff --git a/lib/models/user/history.dart b/lib/models/user/history.dart index 669874b4..0bcf434d 100644 --- a/lib/models/user/history.dart +++ b/lib/models/user/history.dart @@ -79,6 +79,7 @@ class HisListItem { this.kid, this.tagName, this.liveStatus, + this.checked, }); String? title; @@ -105,6 +106,7 @@ class HisListItem { int? kid; String? tagName; int? liveStatus; + bool? checked; HisListItem.fromJson(Map json) { title = json['title']; @@ -131,6 +133,7 @@ class HisListItem { kid = json['kid']; tagName = json['tag_name']; liveStatus = json['live_status']; + checked = false; } } diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index 2152c161..dde9688e 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -8,11 +8,13 @@ import 'package:pilipala/utils/storage.dart'; class HistoryController extends GetxController { final ScrollController scrollController = ScrollController(); - RxList historyList = [HisListItem()].obs; + RxList historyList = [].obs; RxBool isLoadingMore = false.obs; RxBool pauseStatus = false.obs; Box localCache = GStrorage.localCache; RxBool isLoading = false.obs; + RxBool enableMultiple = false.obs; + RxInt checkedCount = 0.obs; @override void onInit() { @@ -140,6 +142,7 @@ class HistoryController extends GetxController { // 删除已看历史记录 Future onDelHistory() async { + /// TODO 优化 List result = historyList.where((e) => e.progress == -1).toList(); for (HisListItem i in result) { @@ -149,4 +152,46 @@ class HistoryController extends GetxController { } SmartDialog.showToast('操作完成'); } + + // 删除选中的记录 + Future onDelCheckedHistory() async { + SmartDialog.show( + useSystem: true, + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('确认删除所选历史记录吗?'), + actions: [ + TextButton( + onPressed: () => SmartDialog.dismiss(), + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + /// TODO 优化 + await SmartDialog.dismiss(); + SmartDialog.showLoading(msg: '请求中'); + List result = + historyList.where((e) => e.checked!).toList(); + for (HisListItem i in result) { + String resKid = 'archive_${i.kid}'; + await UserHttp.delHistory(resKid); + historyList.removeWhere((e) => e.kid == i.kid); + } + checkedCount.value = 0; + SmartDialog.dismiss(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } } diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 9432f9e1..8fb4300e 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -39,6 +39,20 @@ class _HistoryPageState extends State { ); } + // 选中 + onChoose(index) { + _historyController.historyList[index].checked = + !_historyController.historyList[index].checked!; + _historyController.checkedCount.value = + _historyController.historyList.where((item) => item.checked!).length; + _historyController.historyList.refresh(); + } + + // 更新多选状态 + onUpdateMultiple() { + setState(() {}); + } + @override void dispose() { scrollController.removeListener(() {}); @@ -48,51 +62,115 @@ class _HistoryPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - titleSpacing: 0, - centerTitle: false, - title: Text( - '观看记录', - style: Theme.of(context).textTheme.titleMedium, - ), - actions: [ - PopupMenuButton( - onSelected: (String type) { - // 处理菜单项选择的逻辑 - switch (type) { - case 'pause': - _historyController.onPauseHistory(); - break; - case 'clear': - _historyController.onClearHistory(); - break; - case 'del': - _historyController.onDelHistory(); - break; - default: - } - }, - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - value: 'pause', - child: Obx( - () => Text(!_historyController.pauseStatus.value - ? '暂停观看记录' - : '恢复观看记录'), - ), - ), - const PopupMenuItem( - value: 'clear', - child: Text('清空观看记录'), - ), - const PopupMenuItem( - value: 'del', - child: Text('删除已看记录'), - ), - ], + appBar: AppBarWidget( + visible: _historyController.enableMultiple.value, + child1: AppBar( + titleSpacing: 0, + centerTitle: false, + leading: IconButton( + onPressed: () => Get.back(), + icon: const Icon(Icons.arrow_back_outlined), ), - const SizedBox(width: 6), - ], + title: Text( + '观看记录', + style: Theme.of(context).textTheme.titleMedium, + ), + actions: [ + // TextButton( + // onPressed: () { + // _historyController.enableMultiple.value = true; + // setState(() {}); + // }, + // child: const Text('多选'), + // ), + PopupMenuButton( + onSelected: (String type) { + // 处理菜单项选择的逻辑 + switch (type) { + case 'pause': + _historyController.onPauseHistory(); + break; + case 'clear': + _historyController.onClearHistory(); + break; + case 'del': + _historyController.onDelHistory(); + break; + case 'multiple': + _historyController.enableMultiple.value = true; + setState(() {}); + break; + default: + } + }, + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + value: 'pause', + child: Obx( + () => Text(!_historyController.pauseStatus.value + ? '暂停观看记录' + : '恢复观看记录'), + ), + ), + const PopupMenuItem( + value: 'clear', + child: Text('清空观看记录'), + ), + const PopupMenuItem( + value: 'del', + child: Text('删除已看记录'), + ), + const PopupMenuItem( + value: 'multiple', + child: Text('多选删除'), + ), + ], + ), + const SizedBox(width: 6), + ], + ), + child2: AppBar( + titleSpacing: 0, + centerTitle: false, + leading: IconButton( + onPressed: () { + _historyController.enableMultiple.value = false; + for (var item in _historyController.historyList) { + item.checked = false; + } + _historyController.checkedCount.value = 0; + setState(() {}); + }, + icon: const Icon(Icons.close_outlined), + ), + title: Obx( + () => Text( + '已选择${_historyController.checkedCount.value}项', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + actions: [ + TextButton( + onPressed: () { + for (var item in _historyController.historyList) { + item.checked = true; + } + _historyController.checkedCount.value = + _historyController.historyList.length; + _historyController.historyList.refresh(); + }, + child: const Text('全选'), + ), + TextButton( + onPressed: () => _historyController.onDelCheckedHistory(), + child: Text( + '删除', + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + ), + const SizedBox(width: 6), + ], + ), ), body: RefreshIndicator( onRefresh: () async { @@ -120,6 +198,8 @@ class _HistoryPageState extends State { videoItem: _historyController.historyList[index], ctr: _historyController, + onChoose: () => onChoose(index), + onUpdateMultiple: () => onUpdateMultiple(), ); }, childCount: @@ -155,6 +235,36 @@ class _HistoryPageState extends State { ], ), ), + // bottomNavigationBar: BottomAppBar(), + ); + } +} + +class AppBarWidget extends StatelessWidget implements PreferredSizeWidget { + const AppBarWidget({ + required this.child1, + required this.child2, + required this.visible, + Key? key, + }) : super(key: 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 81c100a7..3efbe240 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -12,13 +12,22 @@ import 'package:pilipala/models/common/business_type.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/pages/history/index.dart'; +import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/utils.dart'; class HistoryItem extends StatelessWidget { final dynamic videoItem; final HistoryController? ctr; - const HistoryItem({super.key, required this.videoItem, this.ctr}); + final Function? onChoose; + final Function? onUpdateMultiple; + const HistoryItem({ + super.key, + required this.videoItem, + this.ctr, + this.onChoose, + this.onUpdateMultiple, + }); @override Widget build(BuildContext context) { @@ -27,6 +36,11 @@ class HistoryItem extends StatelessWidget { String heroTag = Utils.makeHeroTag(aid); return InkWell( onTap: () async { + if (ctr!.enableMultiple.value) { + feedBack(); + onChoose!(); + return; + } if (videoItem.history.business.contains('article')) { int cid = videoItem.history.cid ?? // videoItem.history.oid ?? @@ -117,6 +131,14 @@ class HistoryItem extends StatelessWidget { arguments: {'heroTag': heroTag, 'pic': videoItem.cover}); } }, + onLongPress: () { + if (!ctr!.enableMultiple.value) { + feedBack(); + ctr!.enableMultiple.value = true; + onChoose!(); + onUpdateMultiple!(); + } + }, child: Column( children: [ Padding( @@ -132,51 +154,103 @@ class HistoryItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - src: (videoItem.cover != '' - ? videoItem.cover - : videoItem.covers.first), - width: maxWidth, - height: maxHeight, + Stack( + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + Hero( + tag: heroTag, + child: NetworkImgLayer( + src: (videoItem.cover != '' + ? videoItem.cover + : videoItem.covers.first), + width: maxWidth, + height: maxHeight, + ), + ), + if (!BusinessType + .hiddenDurationType.hiddenDurationType + .contains(videoItem.history.business)) + PBadge( + text: videoItem.progress == -1 + ? '已看完' + : '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}', + right: 6.0, + bottom: 6.0, + type: 'gray', + ), + // 右上角 + if (BusinessType.showBadge.showBadge + .contains( + videoItem.history.business) || + videoItem.history.business == + BusinessType.live.type) + PBadge( + text: videoItem.badge, + top: 6.0, + right: 6.0, + bottom: null, + left: null, + ), + ], + ); + }, + ), + ), + Obx( + () => Positioned.fill( + child: AnimatedOpacity( + opacity: ctr!.enableMultiple.value ? 1 : 0, + duration: const Duration(milliseconds: 200), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.black.withOpacity(0.6), + ), + child: Center( + child: SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + EdgeInsets.zero), + backgroundColor: + MaterialStateProperty.resolveWith( + (states) { + return Colors.white + .withOpacity(0.8); + }, + ), + ), + onPressed: () { + feedBack(); + onChoose!(); + }, + icon: Icon( + Icons.done_all_outlined, + color: videoItem.checked + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .outline, + ), + ), + ), ), ), - if (!BusinessType - .hiddenDurationType.hiddenDurationType - .contains(videoItem.history.business)) - PBadge( - text: videoItem.progress == -1 - ? '已看完' - : '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}', - right: 6.0, - bottom: 6.0, - type: 'gray', - ), - // 右上角 - if (BusinessType.showBadge.showBadge - .contains(videoItem.history.business) || - videoItem.history.business == - BusinessType.live.type) - PBadge( - text: videoItem.badge, - top: 6.0, - right: 6.0, - bottom: null, - left: null, - ), - ], - ); - }, - ), + ), + ), + ), + ], ), VideoContent(videoItem: videoItem, ctr: ctr) ],