From b07cf62bddba96f37090e2fc8977421f399683ae Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 16 Mar 2025 16:35:20 +0800 Subject: [PATCH] mod: medialist: show del btn Closes #451 Signed-off-by: bggRGjQaUbCoE --- lib/pages/video/detail/controller.dart | 29 +- .../detail/widgets/media_list_panel.dart | 279 ++++++++++++++++++ .../detail/widgets/watch_later_list.dart | 244 --------------- lib/plugin/pl_player/view.dart | 2 + lib/plugin/pl_player/widgets/common_btn.dart | 1 + 5 files changed, 309 insertions(+), 246 deletions(-) create mode 100644 lib/pages/video/detail/widgets/media_list_panel.dart delete mode 100644 lib/pages/video/detail/widgets/watch_later_list.dart diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 5acf343a..616c7e50 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -23,7 +23,7 @@ import 'package:PiliPlus/pages/video/detail/post_panel/post_panel.dart'; import 'package:PiliPlus/pages/video/detail/related/controller.dart'; import 'package:PiliPlus/pages/video/detail/reply/controller.dart'; import 'package:PiliPlus/pages/video/detail/widgets/send_danmaku_panel.dart'; -import 'package:PiliPlus/pages/video/detail/widgets/watch_later_list.dart'; +import 'package:PiliPlus/pages/video/detail/widgets/media_list_panel.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; @@ -417,6 +417,31 @@ class VideoDetailController extends GetxController getMediaList(isLoadPrevious: true); } : null, + onDelete: ['watchLater', 'fav'].contains(sourceType) + ? (index) async { + if (sourceType == 'watchLater') { + var res = await UserHttp.toViewDel( + aids: [mediaList[index].aid], + ); + if (res['status']) { + mediaList.removeAt(index); + } + SmartDialog.showToast(res['msg']); + } else { + final item = mediaList[index]; + var res = await VideoHttp.delFav( + ids: ['${item.aid}:${item.type}'], + delIds: '${Get.arguments?['mediaId']}', + ); + if (res['status']) { + mediaList.removeAt(index); + SmartDialog.showToast('取消收藏'); + } else { + SmartDialog.showToast(res['msg']); + } + } + } + : null, ), ); } @@ -1502,7 +1527,7 @@ class VideoDetailController extends GetxController VideoHttp.medialistHistory( desc: _mediaDesc ? 1 : 0, oid: aid, - upperMid: Get.arguments['mediaId'], + upperMid: Get.arguments?['mediaId'], ); } } diff --git a/lib/pages/video/detail/widgets/media_list_panel.dart b/lib/pages/video/detail/widgets/media_list_panel.dart new file mode 100644 index 00000000..66646f4c --- /dev/null +++ b/lib/pages/video/detail/widgets/media_list_panel.dart @@ -0,0 +1,279 @@ +import 'package:PiliPlus/common/widgets/icon_button.dart'; +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/stat/danmu.dart'; +import 'package:PiliPlus/common/widgets/stat/view.dart'; +import 'package:PiliPlus/pages/common/common_collapse_slide_page.dart'; +import 'package:PiliPlus/pages/common/common_slide_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/network_img_layer.dart'; +import 'package:PiliPlus/http/search.dart'; +import 'package:PiliPlus/models/video/later.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +class MediaListPanel extends CommonCollapseSlidePage { + const MediaListPanel({ + super.key, + required this.mediaList, + this.changeMediaList, + this.panelTitle, + required this.getBvId, + required this.loadMoreMedia, + required this.count, + required this.desc, + required this.onReverse, + required this.loadPrevious, + this.onDelete, + }); + + final List mediaList; + final Function? changeMediaList; + final String? panelTitle; + final Function getBvId; + final VoidCallback loadMoreMedia; + final int? count; + final bool desc; + final VoidCallback onReverse; + final Function? loadPrevious; + final ValueChanged? onDelete; + + @override + State createState() => _MediaListPanelState(); +} + +class _MediaListPanelState extends CommonSlidePageState { + final _scrollController = ItemScrollController(); + late RxBool desc; + + @override + void initState() { + super.initState(); + desc = widget.desc.obs; + WidgetsBinding.instance.addPostFrameCallback((_) { + int index = + widget.mediaList.indexWhere((item) => item.bvid == widget.getBvId()); + if (index != -1 && index != 0) { + try { + _scrollController.jumpTo(index: index); + } catch (_) {} + } + }); + } + + @override + Widget get buildPage { + return Material( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + AppBar( + toolbarHeight: 45, + automaticallyImplyLeading: false, + titleSpacing: 16, + title: Text(widget.panelTitle ?? '稍后再看'), + actions: [ + Obx( + () => mediumButton( + tooltip: desc.value ? '顺序播放' : '倒序播放', + icon: desc.value + ? MdiIcons.sortAscending + : MdiIcons.sortDescending, + onPressed: () { + widget.onReverse(); + desc.value = !desc.value; + }, + ), + ), + mediumButton( + tooltip: '关闭', + icon: Icons.close, + onPressed: Get.back, + ), + const SizedBox(width: 14), + ], + ), + Expanded( + child: enableSlide ? slideList() : buildList, + ), + ], + ), + ); + } + + @override + Widget get buildList => widget.loadPrevious != null + ? refreshIndicator( + onRefresh: () async { + await widget.loadPrevious!(); + }, + child: _buildList, + ) + : _buildList; + + Widget get _buildList => Obx( + () { + final showDelBtn = + widget.onDelete != null && widget.mediaList.length > 1; + return ScrollablePositionedList.builder( + itemScrollController: _scrollController, + itemCount: widget.mediaList.length, + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + itemBuilder: ((context, index) { + var item = widget.mediaList[index]; + if (index == widget.mediaList.length - 1 && + (widget.count == null || + widget.mediaList.length < widget.count!)) { + widget.loadMoreMedia(); + } + return InkWell( + onTap: () async { + if (item.type != 2) { + SmartDialog.showToast('不支持播放该类型视频'); + return; + } + Get.back(); + String bvid = item.bvid!; + int? aid = item.id; + String cover = item.cover ?? ''; + final int cid = + item.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); + widget.changeMediaList?.call(bvid, cid, aid, cover); + }, + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 8, + ), + child: LayoutBuilder( + builder: (context, boxConstraints) { + const double width = 120; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (BuildContext context, + BoxConstraints boxConstraints) { + final double maxWidth = + boxConstraints.maxWidth; + final double maxHeight = + boxConstraints.maxHeight; + return Stack( + children: [ + NetworkImgLayer( + src: item.cover ?? '', + width: maxWidth, + height: maxHeight, + ), + PBadge( + text: Utils.timeFormat( + item.duration!), + right: 6.0, + bottom: 6.0, + type: 'gray', + ), + ], + ); + }, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.title as String, + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: + item.bvid == widget.getBvId() + ? FontWeight.bold + : null, + color: item.bvid == widget.getBvId() + ? Theme.of(context) + .colorScheme + .primary + : null, + ), + ), + const Spacer(), + Text( + item.upper?.name as String, + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelMedium! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline, + ), + ), + const SizedBox(height: 2), + Row( + children: [ + statView( + context: context, + theme: 'gray', + view: item.cntInfo!['play'] as int, + ), + const SizedBox(width: 8), + statDanMu( + context: context, + theme: 'gray', + danmu: + item.cntInfo!['danmaku'] as int, + ), + ], + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + if (showDelBtn && item.bvid != widget.getBvId()) + Positioned( + right: 12, + bottom: 0, + child: iconButton( + tooltip: '移除', + context: context, + onPressed: () { + widget.onDelete!(index); + }, + icon: Icons.clear, + iconColor: + Theme.of(context).colorScheme.onSurfaceVariant, + bgColor: Colors.transparent, + ), + ), + ], + ), + ); + }), + ); + }, + ); +} diff --git a/lib/pages/video/detail/widgets/watch_later_list.dart b/lib/pages/video/detail/widgets/watch_later_list.dart deleted file mode 100644 index 063af953..00000000 --- a/lib/pages/video/detail/widgets/watch_later_list.dart +++ /dev/null @@ -1,244 +0,0 @@ -import 'package:PiliPlus/common/widgets/icon_button.dart'; -import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; -import 'package:PiliPlus/common/widgets/stat/danmu.dart'; -import 'package:PiliPlus/common/widgets/stat/view.dart'; -import 'package:PiliPlus/pages/common/common_slide_page.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; -import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/badge.dart'; -import 'package:PiliPlus/common/widgets/network_img_layer.dart'; -import 'package:PiliPlus/http/search.dart'; -import 'package:PiliPlus/models/video/later.dart'; -import 'package:PiliPlus/utils/utils.dart'; -import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; - -class MediaListPanel extends CommonSlidePage { - const MediaListPanel({ - super.key, - required this.mediaList, - this.changeMediaList, - this.panelTitle, - required this.getBvId, - required this.loadMoreMedia, - required this.count, - required this.desc, - required this.onReverse, - required this.loadPrevious, - }); - - final List mediaList; - final Function? changeMediaList; - final String? panelTitle; - final Function getBvId; - final VoidCallback loadMoreMedia; - final int? count; - final bool desc; - final VoidCallback onReverse; - final Function? loadPrevious; - - @override - State createState() => _MediaListPanelState(); -} - -class _MediaListPanelState extends CommonSlidePageState { - final _scrollController = ItemScrollController(); - late RxBool desc; - - @override - void initState() { - super.initState(); - desc = widget.desc.obs; - WidgetsBinding.instance.addPostFrameCallback((_) { - int index = - widget.mediaList.indexWhere((item) => item.bvid == widget.getBvId()); - if (index != -1 && index != 0) { - try { - _scrollController.jumpTo(index: index); - } catch (_) {} - } - }); - } - - @override - Widget get buildPage { - return Material( - color: Theme.of(context).colorScheme.surface, - child: Column( - children: [ - AppBar( - toolbarHeight: 45, - automaticallyImplyLeading: false, - titleSpacing: 16, - title: Text(widget.panelTitle ?? '稍后再看'), - actions: [ - Obx( - () => mediumButton( - tooltip: desc.value ? '顺序播放' : '倒序播放', - icon: desc.value - ? MdiIcons.sortAscending - : MdiIcons.sortDescending, - onPressed: () { - widget.onReverse(); - desc.value = !desc.value; - }, - ), - ), - mediumButton( - tooltip: '关闭', - icon: Icons.close, - onPressed: Get.back, - ), - const SizedBox(width: 14), - ], - ), - Expanded( - child: enableSlide ? slideList() : buildList, - ), - ], - ), - ); - } - - @override - Widget get buildList => widget.loadPrevious != null - ? refreshIndicator( - onRefresh: () async { - await widget.loadPrevious!(); - }, - child: _buildList, - ) - : _buildList; - - Widget get _buildList => Obx( - () => ScrollablePositionedList.builder( - itemScrollController: _scrollController, - itemCount: widget.mediaList.length, - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80, - ), - itemBuilder: ((context, index) { - var item = widget.mediaList[index]; - if (index == widget.mediaList.length - 1 && - (widget.count == null || - widget.mediaList.length < widget.count!)) { - widget.loadMoreMedia(); - } - return InkWell( - onTap: () async { - if (item.type != 2) { - SmartDialog.showToast('不支持播放该类型视频'); - return; - } - Get.back(); - String bvid = item.bvid!; - int? aid = item.id; - String cover = item.cover ?? ''; - final int cid = - item.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); - widget.changeMediaList?.call(bvid, cid, aid, cover); - }, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 8, - ), - child: LayoutBuilder( - builder: (context, boxConstraints) { - const double width = 120; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder( - builder: (BuildContext context, - BoxConstraints boxConstraints) { - final double maxWidth = boxConstraints.maxWidth; - final double maxHeight = - boxConstraints.maxHeight; - return Stack( - children: [ - NetworkImgLayer( - src: item.cover ?? '', - width: maxWidth, - height: maxHeight, - ), - PBadge( - text: Utils.timeFormat(item.duration!), - right: 6.0, - bottom: 6.0, - type: 'gray', - ), - ], - ); - }, - ), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.title as String, - textAlign: TextAlign.start, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontWeight: item.bvid == widget.getBvId() - ? FontWeight.bold - : null, - color: item.bvid == widget.getBvId() - ? Theme.of(context).colorScheme.primary - : null, - ), - ), - const Spacer(), - Text( - item.upper?.name as String, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize, - color: - Theme.of(context).colorScheme.outline, - ), - ), - const SizedBox(height: 2), - Row( - children: [ - statView( - context: context, - theme: 'gray', - view: item.cntInfo!['play'] as int, - ), - const SizedBox(width: 8), - statDanMu( - context: context, - theme: 'gray', - danmu: item.cntInfo!['danmaku'] as int, - ), - ], - ), - ], - ), - ), - ], - ), - ); - }, - ), - ), - ); - }), - ), - ); -} diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 7aa51417..0be3d8f2 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -12,6 +12,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -450,6 +451,7 @@ class _PLVideoPlayerState extends State ), onTap: widget.showViewPoints, onLongPress: () { + Feedback.forLongPress(context); plPlayerController.showVP.value = !plPlayerController.showVP.value; }, diff --git a/lib/plugin/pl_player/widgets/common_btn.dart b/lib/plugin/pl_player/widgets/common_btn.dart index 0f32ad99..484367bf 100644 --- a/lib/plugin/pl_player/widgets/common_btn.dart +++ b/lib/plugin/pl_player/widgets/common_btn.dart @@ -20,6 +20,7 @@ class ComBtn extends StatelessWidget { child: GestureDetector( onTap: onTap, onLongPress: onLongPress, + behavior: HitTestBehavior.opaque, child: icon, ), );