From bc2de4828b4dd35b33a5dd0d4504a5f68fa22b9f Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Tue, 10 Jun 2025 16:18:51 +0800 Subject: [PATCH] refa dm block Signed-off-by: bggRGjQaUbCoE --- lib/http/danmaku_block.dart | 5 +- lib/models/common/dm_block_type.dart | 9 ++ lib/models/user/danmaku_block.dart | 26 +++- lib/models/user/danmaku_rule.dart | 29 ++-- lib/pages/danmaku_block/controller.dart | 35 +++-- lib/pages/danmaku_block/view.dart | 198 +++++++++++++----------- 6 files changed, 167 insertions(+), 135 deletions(-) create mode 100644 lib/models/common/dm_block_type.dart diff --git a/lib/http/danmaku_block.dart b/lib/http/danmaku_block.dart index 8a1c91aa..10a56c28 100644 --- a/lib/http/danmaku_block.dart +++ b/lib/http/danmaku_block.dart @@ -28,10 +28,7 @@ class DanmakuFilterHttp { }, ); if (res.data['code'] == 0) { - return { - 'status': true, - 'msg': '操作成功', - }; + return {'status': true}; } else { return { 'status': false, diff --git a/lib/models/common/dm_block_type.dart b/lib/models/common/dm_block_type.dart new file mode 100644 index 00000000..cee31e94 --- /dev/null +++ b/lib/models/common/dm_block_type.dart @@ -0,0 +1,9 @@ +enum DmBlockType { + keyword('关键词'), + regex('正则'), + uid('用户'), + ; + + final String label; + const DmBlockType(this.label); +} diff --git a/lib/models/user/danmaku_block.dart b/lib/models/user/danmaku_block.dart index bcb78a09..bb086e87 100644 --- a/lib/models/user/danmaku_block.dart +++ b/lib/models/user/danmaku_block.dart @@ -1,13 +1,28 @@ class DanmakuBlockDataModel { - List? rule; + late List rule; + late List rule1; + late List rule2; String? toast; int? valid; int? ver; - DanmakuBlockDataModel({this.rule, this.toast, this.valid, this.ver}); - DanmakuBlockDataModel.fromJson(Map json) { - rule = (json['rule'] as List?)?.map((v) => SimpleRule.fromJson(v)).toList(); + rule = []; + rule1 = []; + rule2 = []; + if ((json['rule'] as List?)?.isNotEmpty == true) { + for (var e in json['rule']) { + SimpleRule item = SimpleRule.fromJson(e); + switch (item.type) { + case 0: + rule.add(item); + case 1: + rule1.add(item); + case 2: + rule2.add(item); + } + } + } toast = json['toast'] == '' ? null : json['toast']; valid = json['valid']; ver = json['ver']; @@ -17,8 +32,7 @@ class DanmakuBlockDataModel { class SimpleRule { late final int id; late final int type; - late String filter; - SimpleRule(this.id, this.type, this.filter); + late final String filter; SimpleRule.fromJson(Map json) { id = json['id']; diff --git a/lib/models/user/danmaku_rule.dart b/lib/models/user/danmaku_rule.dart index ae9b71e1..fb26d6e6 100644 --- a/lib/models/user/danmaku_rule.dart +++ b/lib/models/user/danmaku_rule.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart'; +import 'package:PiliPlus/models/user/danmaku_block.dart'; class RuleFilter { static final _regExp = RegExp(r'^/(.*)/$'); @@ -14,23 +15,17 @@ class RuleFilter { count ?? dmFilterString.length + dmRegExp.length + dmUid.length; } - RuleFilter.fromRuleTypeEntires( - Iterable>> rules) { - for (var rule in rules) { - switch (rule.key) { - case 0: - dmFilterString.addAll(rule.value.values); - break; - case 1: - dmRegExp.addAll(rule.value.values.map((i) => RegExp( - _regExp.matchAsPrefix(i)?.group(1) ?? i, - caseSensitive: false))); - break; - case 2: - dmUid.addAll(rule.value.values); - break; - } - } + RuleFilter.fromRuleTypeEntires(List> rules) { + dmFilterString = rules[0].map((e) => e.filter).toList(); + + dmRegExp = rules[1] + .map((e) => RegExp( + _regExp.matchAsPrefix(e.filter)?.group(1) ?? e.filter, + caseSensitive: false)) + .toList(); + + dmUid = rules[2].map((e) => e.filter).toSet(); + count = dmFilterString.length + dmRegExp.length + dmUid.length; } diff --git a/lib/pages/danmaku_block/controller.dart b/lib/pages/danmaku_block/controller.dart index cab13b8a..ec36ade1 100644 --- a/lib/pages/danmaku_block/controller.dart +++ b/lib/pages/danmaku_block/controller.dart @@ -1,17 +1,24 @@ +import 'dart:convert'; + import 'package:PiliPlus/http/danmaku_block.dart'; +import 'package:PiliPlus/models/common/dm_block_type.dart'; import 'package:PiliPlus/models/user/danmaku_block.dart'; +import 'package:crclib/catalog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class DanmakuBlockController extends GetxController with GetSingleTickerProviderStateMixin { - final ruleTypes = RxMap>({0: {}, 1: {}, 2: {}}); + late final List> rules = + List.generate(DmBlockType.values.length, (_) => [].obs); + late TabController tabController; @override void onInit() { super.onInit(); + queryDanmakuFilter(); tabController = TabController(length: 3, vsync: this); } @@ -27,12 +34,9 @@ class DanmakuBlockController extends GetxController SmartDialog.dismiss(); if (result['status']) { DanmakuBlockDataModel data = result['data']; - if (data.rule?.isNotEmpty == true) { - for (var rule in data.rule!) { - ruleTypes[rule.type]![rule.id] = rule.filter; - } - ruleTypes.refresh(); - } + rules[0].addAll(data.rule); + rules[1].addAll(data.rule1); + rules[2].addAll(data.rule2); if (data.toast != null) { SmartDialog.showToast(data.toast!); } @@ -41,29 +45,30 @@ class DanmakuBlockController extends GetxController } } - Future danmakuFilterDel(int type, int id) async { + Future danmakuFilterDel(int tabIndex, int itemIndex, int id) async { SmartDialog.showLoading(msg: '正在删除弹幕屏蔽规则……'); var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id); SmartDialog.dismiss(); if (result['status']) { - ruleTypes - ..[type]!.remove(id) - ..refresh(); + rules[tabIndex].removeAt(itemIndex); + SmartDialog.showToast('删除成功'); + } else { + SmartDialog.showToast(result['msg']); } - SmartDialog.showToast(result['msg']); } Future danmakuFilterAdd( {required String filter, required int type}) async { + if (type == 2) { + filter = Crc32Xz().convert(utf8.encode(filter)).toRadixString(16); + } SmartDialog.showLoading(msg: '正在添加弹幕屏蔽规则……'); var result = await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type); SmartDialog.dismiss(); if (result['status']) { SimpleRule rule = result['data']; - ruleTypes - ..[type]![rule.id] = rule.filter - ..refresh(); + rules[type].add(rule); SmartDialog.showToast('添加成功'); } else { SmartDialog.showToast(result['msg']); diff --git a/lib/pages/danmaku_block/view.dart b/lib/pages/danmaku_block/view.dart index 42a4076c..0ae43814 100644 --- a/lib/pages/danmaku_block/view.dart +++ b/lib/pages/danmaku_block/view.dart @@ -1,9 +1,15 @@ +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/models/common/dm_block_type.dart'; +import 'package:PiliPlus/models/user/danmaku_block.dart'; import 'package:PiliPlus/models/user/danmaku_rule.dart'; import 'package:PiliPlus/pages/danmaku_block/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -15,75 +21,130 @@ class DanmakuBlockPage extends StatefulWidget { } class _DanmakuBlockPageState extends State { - final DanmakuBlockController _danmakuBlockController = - Get.put(DanmakuBlockController()); - final ScrollController scrollController = ScrollController(); + final DanmakuBlockController _controller = Get.put(DanmakuBlockController()); late PlPlayerController plPlayerController; - static const Map ruleLabels = { - 0: '关键词', - 1: '正则', - 2: '用户', - }; - @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - _danmakuBlockController.queryDanmakuFilter(); - }); plPlayerController = Get.arguments as PlPlayerController; } @override void dispose() { - final ruleFilter = RuleFilter.fromRuleTypeEntires( - _danmakuBlockController.ruleTypes.entries); + final ruleFilter = RuleFilter.fromRuleTypeEntires(_controller.rules); plPlayerController.filters = ruleFilter; - scrollController.dispose(); GStorage.localCache.put(LocalCacheKey.danmakuFilterRules, ruleFilter); 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; + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: const Text('弹幕屏蔽'), + bottom: TabBar( + controller: _controller.tabController, + tabs: DmBlockType.values + .map((e) => Obx(() => Tab( + text: '${e.label}(${_controller.rules[e.index].length})'))) + .toList(), + ), + ), + body: tabBarView( + controller: _controller.tabController, + children: DmBlockType.values + .map((e) => KeepAliveWrapper( + builder: (context) => Obx(() => + tabViewBuilder(e.index, _controller.rules[e.index])), + )) + .toList(), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => + _showAddDialog(DmBlockType.values[_controller.tabController.index]), + child: const Icon(Icons.add), + ), + ); + } + + Widget tabViewBuilder(int tabIndex, List list) { + if (list.isEmpty) { + return scrollErrorWidget(); } + return ListView.builder( + itemCount: list.length, + padding: + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom + 80), + itemBuilder: (context, itemIndex) { + final SimpleRule item = list[itemIndex]; + return ListTile( + title: Text( + item.filter, + style: Theme.of(context).textTheme.bodyMedium, + ), + trailing: IconButton( + icon: const Icon(Icons.delete_outlined), + onPressed: () => showConfirmDialog( + context: context, + title: '确定删除该规则?', + onConfirm: () => _controller.danmakuFilterDel( + tabIndex, + itemIndex, + item.id, + ), + ), + ), + ); + }, + ); + } + + void _showAddDialog(DmBlockType type) { + String filter = ''; + String hintText = switch (type) { + DmBlockType.keyword => '输入过滤的关键词,其它类别请切换标签页后添加', + DmBlockType.regex => '输入//之间的正则表达式,无需包含头尾的"/"', + DmBlockType.uid => '输入用户UID', + }; + final isUid = type == DmBlockType.uid; showDialog( context: context, - builder: (BuildContext context) { + builder: (context) { return AlertDialog( - title: Text('添加新的${ruleLabels[type]}规则'), - content: Column(mainAxisSize: MainAxisSize.min, children: [ - Text(hintText), - TextField( - controller: textController, - autofocus: true, - ) - ]), - actions: [ + title: Text('添加新的${type.label}规则'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(hintText), + TextFormField( + autofocus: true, + initialValue: filter, + onChanged: (value) => filter = value, + keyboardType: isUid ? TextInputType.number : null, + inputFormatters: isUid + ? [FilteringTextInputFormatter.allow(RegExp(r'\d+'))] + : null, + ) + ], + ), + actions: [ TextButton( - onPressed: Navigator.of(context).pop, - child: const Text('取消'), + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), ), TextButton( child: const Text('添加'), onPressed: () { - String filter = textController.text; if (filter.isNotEmpty) { - _danmakuBlockController.danmakuFilterAdd( - filter: filter, type: type); - Navigator.of(context).pop(); + Get.back(); + _controller.danmakuFilterAdd( + filter: filter, type: type.index); } else { SmartDialog.showToast('输入内容不能为空'); } @@ -94,53 +155,4 @@ class _DanmakuBlockPageState extends State { }, ); } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: TabBar(controller: _danmakuBlockController.tabController, tabs: [ - for (var i = 0; i < ruleLabels.length; i++) - Obx(() => Tab( - text: - '${ruleLabels[i]}(${_danmakuBlockController.ruleTypes[i]!.length})')), - ]), - ), - body: tabBarView( - controller: _danmakuBlockController.tabController, - children: [ - for (var i = 0; i < ruleLabels.length; i++) - Obx(() => tabViewBuilder( - i, _danmakuBlockController.ruleTypes[i]!.entries.toList())), - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () => - _showAddDialog(_danmakuBlockController.tabController.index), - child: const Icon(Icons.add), - ), - ); - } - - Widget tabViewBuilder(int tabIndex, List> list) { - return ListView.builder( - controller: scrollController, - itemCount: list.length, - padding: const EdgeInsets.only(bottom: 100), - itemBuilder: (BuildContext context, int listIndex) { - return ListTile( - title: Text( - list[listIndex].value, - style: Theme.of(context).textTheme.bodyMedium, - ), - trailing: IconButton( - icon: const Icon(Icons.delete), - onPressed: () => _danmakuBlockController.danmakuFilterDel( - tabIndex, list[listIndex].key), - ), - ); - }, - ); - } }