From e9c64694cee134cf6227bd35cb8454aa7c2163de Mon Sep 17 00:00:00 2001 From: orz12 Date: Fri, 24 May 2024 10:53:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=A8=E5=BC=B9=E5=B9=95?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E8=AF=8D=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/user/danmaku_block.dart | 2 +- lib/pages/danmaku/controller.dart | 38 +++- lib/pages/danmaku/view.dart | 4 +- lib/pages/danmaku_block/index.dart | 194 +++++++++++------- .../video/detail/widgets/header_control.dart | 9 +- lib/plugin/pl_player/controller.dart | 12 +- 6 files changed, 179 insertions(+), 80 deletions(-) diff --git a/lib/models/user/danmaku_block.dart b/lib/models/user/danmaku_block.dart index ef0b6a6a..489397de 100644 --- a/lib/models/user/danmaku_block.dart +++ b/lib/models/user/danmaku_block.dart @@ -74,7 +74,7 @@ class Rule { class SimpleRule { final int id; final int type; - final String filter; + String filter; SimpleRule(this.id, this.type, this.filter); Map toMap() { return { diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart index a998303f..c7f5c40a 100644 --- a/lib/pages/danmaku/controller.dart +++ b/lib/pages/danmaku/controller.dart @@ -3,10 +3,12 @@ import 'package:PiliPalaX/models/danmaku/dm.pb.dart'; import 'package:flutter/cupertino.dart'; class PlDanmakuController { - PlDanmakuController(this.cid, this.danmakuWeightNotifier); + PlDanmakuController(this.cid, this.danmakuWeightNotifier, this.danmakuFilterNotifier); final int cid; final ValueNotifier danmakuWeightNotifier; + final ValueNotifier>> danmakuFilterNotifier; int danmakuWeight = 0; + List> danmakuFilter = []; Map> dmSegMap = {}; // 已请求的段落标记 List requestedSeg = []; @@ -24,6 +26,10 @@ class PlDanmakuController { "danmakuWeight changed from $danmakuWeight to ${danmakuWeightNotifier.value}"); danmakuWeight = danmakuWeightNotifier.value; }); + danmakuFilterNotifier.addListener(() { + print("danmakuFilter changed from $danmakuFilter to ${danmakuFilterNotifier.value}"); + danmakuFilter = danmakuFilterNotifier.value; + }); if (requestedSeg.isEmpty) { int segCount = (videoDuration / segmentLength).ceil(); requestedSeg = List.generate(segCount, (index) => false); @@ -32,6 +38,9 @@ class PlDanmakuController { } void dispose() { + danmakuWeightNotifier.removeListener(() {}); + danmakuFilterNotifier.removeListener(() {}); + danmakuFilter.clear(); dmSegMap.clear(); requestedSeg.clear(); } @@ -67,13 +76,36 @@ class PlDanmakuController { if (!requestedSeg[segmentIndex]) { queryDanmaku(segmentIndex); } - if (danmakuWeight == 0) { + if (danmakuWeight == 0 && danmakuFilter.isEmpty) { return dmSegMap[progress ~/ 100]; } else { - //using filter return dmSegMap[progress ~/ 100] ?.where((element) => element.weight >= danmakuWeight) + .where(filterDanmaku) .toList(); } } + + bool filterDanmaku(DanmakuElem elem) { + for (var filter in danmakuFilter) { + switch (filter['type']) { + case 0: + if (elem.content.contains(filter['filter'])) { + return false; + } + break; + case 1: + if (RegExp(filter['filter']).hasMatch(elem.content)) { + return false; + } + break; + case 2: + if (elem.idStr == filter['filter']) { + return false; + } + break; + } + } + return true; + } } diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index d14d238c..89cf8c3e 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -45,7 +45,9 @@ class _PlDanmakuState extends State { enableShowDanmaku = setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); _plDanmakuController = PlDanmakuController( - widget.cid, widget.playerController.danmakuWeight); + widget.cid, + widget.playerController.danmakuWeight, + widget.playerController.danmakuFilterRule); if (mounted) { playerController = widget.playerController; if (enableShowDanmaku || playerController.isOpenDanmu.value) { diff --git a/lib/pages/danmaku_block/index.dart b/lib/pages/danmaku_block/index.dart index 512dd359..dc8e6ade 100644 --- a/lib/pages/danmaku_block/index.dart +++ b/lib/pages/danmaku_block/index.dart @@ -6,6 +6,7 @@ import 'package:PiliPalaX/utils/storage.dart'; import '../../http/danmaku_block.dart'; import '../../models/user/danmaku_block.dart'; +import '../../plugin/pl_player/controller.dart'; class DanmakuBlockPage extends StatefulWidget { const DanmakuBlockPage({super.key}); @@ -19,71 +20,143 @@ class _DanmakuBlockPageState extends State { Get.put(DanmakuBlockController()); final ScrollController scrollController = ScrollController(); Box setting = GStrorage.setting; + late PlPlayerController plPlayerController; + + static const Map ruleLabels = { + 0: '关键词', + 1: '正则', + 2: '用户', + }; @override void initState() { super.initState(); - _danmakuBlockController.queryDanmakuFilter(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _danmakuBlockController.queryDanmakuFilter(); + }); + plPlayerController = Get.arguments as PlPlayerController; } @override void dispose() { - List> simpleRuleList = - _danmakuBlockController.danmakuRules.map>((e) { - return SimpleRule(e.id!, e.type!, e.filter!).toMap(); + List> simpleRuleList = _danmakuBlockController + .ruleTypes.values + .expand((element) => element) + .map>((e) { + //当正则表达式前后都有"/"时,去掉,避免RegExp解析错误 + if (e.type == 1 && e.filter.startsWith('/') && e.filter.endsWith('/')) { + e.filter = e.filter.substring(1, e.filter.length - 1); + } + return e.toMap(); }).toList(); + print("simpleRuleList:$simpleRuleList"); setting.put(SettingBoxKey.danmakuFilterRule, simpleRuleList); + plPlayerController.danmakuFilterRule.value = simpleRuleList; scrollController.removeListener(() {}); + scrollController.dispose(); super.dispose(); } + void _showAddDialog(int type) { + final TextEditingController textController = TextEditingController(); + late String hintText; + switch (type) { + case 0: + hintText = '输入过滤的关键词,其它类别请切换标签页后添加'; + break; + case 1: + hintText = '输入//之间的正则表达式,无需包含头尾的"/"'; + break; + case 2: + hintText = '输入经CRC32B(即小写16进制的CRC32)哈希后的用户UID'; + break; + } + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('添加新的${ruleLabels[type]}规则'), + content: Column(mainAxisSize: MainAxisSize.min, children: [ + Text(hintText), + TextField( + controller: textController, + //decoration: InputDecoration(hintText: hintText), + ) + ]), + actions: [ + TextButton( + child: const Text('取消'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('添加'), + onPressed: () async { + String filter = textController.text; + if (filter.isNotEmpty) { + await _danmakuBlockController.danmakuFilterAdd( + filter: filter, type: type); + if (!context.mounted) return; + Navigator.of(context).pop(); + } else { + SmartDialog.showToast('输入内容不能为空'); + } + }, + ), + ], + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Obx( - () => TabBar( + title: TabBar( controller: _danmakuBlockController.tabController, dividerColor: Colors.transparent, tabs: [ - Tab(text: '文本(${_danmakuBlockController.textRules.length})'), - Tab(text: '正则(${_danmakuBlockController.regexRules.length})'), - Tab(text: '用户(${_danmakuBlockController.userRules.length})'), - ], - ), - ), + for (var i = 0; i < ruleLabels.length; i++) + Obx(() => Tab( + text: + '${ruleLabels[i]}(${_danmakuBlockController.ruleTypes[i]!.length})')), + ]), ), - body: RefreshIndicator( - onRefresh: () async => - await _danmakuBlockController.queryDanmakuFilter(), - child: TabBarView( - controller: _danmakuBlockController.tabController, - children: [ - Obx(() => tabViewBuilder(0, _danmakuBlockController.textRules)), - Obx(() => tabViewBuilder(1, _danmakuBlockController.regexRules)), - Obx(() => tabViewBuilder(2, _danmakuBlockController.userRules)), - ], - ), + body: TabBarView( + controller: _danmakuBlockController.tabController, + children: [ + for (var i = 0; i < ruleLabels.length; i++) + Obx(() => tabViewBuilder(i, _danmakuBlockController.ruleTypes[i]!)), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + _showAddDialog(_danmakuBlockController.tabController.index); + }, + child: const Icon(Icons.add), ), ); } - Widget tabViewBuilder(int index, List list) { + + Widget tabViewBuilder(int tabIndex, List list) { return ListView.builder( controller: scrollController, itemCount: list.length, - itemBuilder: (BuildContext context, int index) { + padding: const EdgeInsets.only(bottom: 100), + itemBuilder: (BuildContext context, int listIndex) { return ListTile( title: Text( - list[index].filter, + list[listIndex].filter, style: Theme.of(context).textTheme.subtitle1, ), trailing: IconButton( - icon: const Icon(Icons.delete), - onPressed: () async { - await _danmakuBlockController.danmakuFilterDel( - 1, list[index].id); - }, - ), + icon: const Icon(Icons.delete), + onPressed: () async { + await _danmakuBlockController.danmakuFilterDel( + tabIndex, list[listIndex].id); + }), ); }, ); @@ -93,9 +166,11 @@ class _DanmakuBlockPageState extends State { class DanmakuBlockController extends GetxController with GetTickerProviderStateMixin { RxList danmakuRules = [].obs; - RxList textRules = [].obs; - RxList regexRules = [].obs; - RxList userRules = [].obs; + RxMap> ruleTypes = { + 0: [], + 1: [], + 2: [], + }.obs; late TabController tabController; @override @@ -111,45 +186,31 @@ class DanmakuBlockController extends GetxController } Future queryDanmakuFilter() async { + SmartDialog.showLoading(msg: '正在同步弹幕屏蔽规则……'); var result = await DanmakuFilterHttp.danmakuFilter(); + SmartDialog.dismiss(); if (result['status']) { danmakuRules.value = result['data'].rule; danmakuRules.map((e) { SimpleRule simpleRule = SimpleRule(e.id!, e.type!, e.filter!); - switch (e.type!) { - case 0: - textRules.add(simpleRule); - break; - case 1: - regexRules.add(simpleRule); - break; - case 2: - userRules.add(simpleRule); - break; - default: - SmartDialog.showToast('未知的规则类型:${e.type},内容为:${e.filter}'); - } + ruleTypes[e.type!]!.add(simpleRule); }).toList(); + ruleTypes.refresh(); SmartDialog.showToast(result['data'].toast); + } else { + SmartDialog.showToast(result['msg']); } return result; } Future danmakuFilterDel(int type, int id) async { + SmartDialog.showLoading(msg: '正在删除弹幕屏蔽规则……'); var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id); + SmartDialog.dismiss(); if (result['status']) { danmakuRules.removeWhere((e) => e.id == id); - switch (type) { - case 0: - textRules.removeWhere((e) => e.id == id); - break; - case 1: - regexRules.removeWhere((e) => e.id == id); - break; - case 2: - userRules.removeWhere((e) => e.id == id); - break; - } + ruleTypes[type]!.removeWhere((e) => e.id == id); + ruleTypes.refresh(); SmartDialog.showToast(result['msg']); } else { SmartDialog.showToast(result['msg']); @@ -157,23 +218,16 @@ class DanmakuBlockController extends GetxController } Future danmakuFilterAdd({required String filter, required int type}) async { + SmartDialog.showLoading(msg: '正在添加弹幕屏蔽规则……'); var result = await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type); + SmartDialog.dismiss(); if (result['status']) { Rule data = result['data']; danmakuRules.add(data); SimpleRule simpleRule = SimpleRule(data.id!, data.type!, data.filter!); - switch (data.type!) { - case 0: - textRules.add(simpleRule); - break; - case 1: - regexRules.add(simpleRule); - break; - case 2: - userRules.add(simpleRule); - break; - } + ruleTypes[type]!.add(simpleRule); + ruleTypes.refresh(); SmartDialog.showToast('添加成功'); } else { SmartDialog.showToast(result['msg']); diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index a531c6bd..1c75f627 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -774,8 +774,13 @@ class _HeaderControlState extends State { minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - onPressed: () => Get.toNamed('/danmakuBlock'), - child: const Text("屏蔽管理")) + onPressed: () => { + Get.back(), + Get.toNamed('/danmakuBlock', + arguments: widget.controller) + }, + child: Text( + "屏蔽管理(${widget.controller!.danmakuFilterRule.value.length})")), ], ), Padding( diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 9f9dd929..d381a580 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -235,6 +235,8 @@ class PlPlayerController { /// 弹幕权重 ValueNotifier danmakuWeight = ValueNotifier(0); + ValueNotifier>> danmakuFilterRule = + ValueNotifier([]); // 关联弹幕控制器 DanmakuController? danmakuController; // 弹幕相关配置 @@ -287,6 +289,10 @@ class PlPlayerController { setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); danmakuWeight.value = setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0); + danmakuFilterRule.value = setting.get(SettingBoxKey.danmakuFilterRule, + defaultValue: []).map>((e) { + return Map.from(e); + }).toList(); blockTypes = setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []); showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5); // 不透明度 @@ -312,8 +318,8 @@ class PlPlayerController { enableAutoLongPressSpeed = setting .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); // 后台播放 - _continuePlayInBackground.value = - setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: false); + _continuePlayInBackground.value = setting + .get(SettingBoxKey.continuePlayInBackground, defaultValue: false); if (!enableAutoLongPressSpeed) { _longPressSpeed.value = videoStorage .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0); @@ -515,7 +521,7 @@ class PlPlayerController { configuration: VideoControllerConfiguration( enableHardwareAcceleration: enableHA, androidAttachSurfaceAfterVideoParameters: false, - hwdec: enableHA ? hwdec: null, + hwdec: enableHA ? hwdec : null, ), );