mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-21 01:26:59 +08:00
refa dm block
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -28,10 +28,7 @@ class DanmakuFilterHttp {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {'status': true};
|
||||||
'status': true,
|
|
||||||
'msg': '操作成功',
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
|
|||||||
9
lib/models/common/dm_block_type.dart
Normal file
9
lib/models/common/dm_block_type.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
enum DmBlockType {
|
||||||
|
keyword('关键词'),
|
||||||
|
regex('正则'),
|
||||||
|
uid('用户'),
|
||||||
|
;
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
const DmBlockType(this.label);
|
||||||
|
}
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
class DanmakuBlockDataModel {
|
class DanmakuBlockDataModel {
|
||||||
List<SimpleRule>? rule;
|
late List<SimpleRule> rule;
|
||||||
|
late List<SimpleRule> rule1;
|
||||||
|
late List<SimpleRule> rule2;
|
||||||
String? toast;
|
String? toast;
|
||||||
int? valid;
|
int? valid;
|
||||||
int? ver;
|
int? ver;
|
||||||
|
|
||||||
DanmakuBlockDataModel({this.rule, this.toast, this.valid, this.ver});
|
|
||||||
|
|
||||||
DanmakuBlockDataModel.fromJson(Map<String, dynamic> json) {
|
DanmakuBlockDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
rule = (json['rule'] as List?)?.map((v) => SimpleRule.fromJson(v)).toList();
|
rule = <SimpleRule>[];
|
||||||
|
rule1 = <SimpleRule>[];
|
||||||
|
rule2 = <SimpleRule>[];
|
||||||
|
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'];
|
toast = json['toast'] == '' ? null : json['toast'];
|
||||||
valid = json['valid'];
|
valid = json['valid'];
|
||||||
ver = json['ver'];
|
ver = json['ver'];
|
||||||
@@ -17,8 +32,7 @@ class DanmakuBlockDataModel {
|
|||||||
class SimpleRule {
|
class SimpleRule {
|
||||||
late final int id;
|
late final int id;
|
||||||
late final int type;
|
late final int type;
|
||||||
late String filter;
|
late final String filter;
|
||||||
SimpleRule(this.id, this.type, this.filter);
|
|
||||||
|
|
||||||
SimpleRule.fromJson(Map<String, dynamic> json) {
|
SimpleRule.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['id'];
|
id = json['id'];
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
|
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
|
||||||
|
import 'package:PiliPlus/models/user/danmaku_block.dart';
|
||||||
|
|
||||||
class RuleFilter {
|
class RuleFilter {
|
||||||
static final _regExp = RegExp(r'^/(.*)/$');
|
static final _regExp = RegExp(r'^/(.*)/$');
|
||||||
@@ -14,23 +15,17 @@ class RuleFilter {
|
|||||||
count ?? dmFilterString.length + dmRegExp.length + dmUid.length;
|
count ?? dmFilterString.length + dmRegExp.length + dmUid.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
RuleFilter.fromRuleTypeEntires(
|
RuleFilter.fromRuleTypeEntires(List<List<SimpleRule>> rules) {
|
||||||
Iterable<MapEntry<int, Map<int, String>>> rules) {
|
dmFilterString = rules[0].map((e) => e.filter).toList();
|
||||||
for (var rule in rules) {
|
|
||||||
switch (rule.key) {
|
dmRegExp = rules[1]
|
||||||
case 0:
|
.map((e) => RegExp(
|
||||||
dmFilterString.addAll(rule.value.values);
|
_regExp.matchAsPrefix(e.filter)?.group(1) ?? e.filter,
|
||||||
break;
|
caseSensitive: false))
|
||||||
case 1:
|
.toList();
|
||||||
dmRegExp.addAll(rule.value.values.map((i) => RegExp(
|
|
||||||
_regExp.matchAsPrefix(i)?.group(1) ?? i,
|
dmUid = rules[2].map((e) => e.filter).toSet();
|
||||||
caseSensitive: false)));
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
dmUid.addAll(rule.value.values);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
count = dmFilterString.length + dmRegExp.length + dmUid.length;
|
count = dmFilterString.length + dmRegExp.length + dmUid.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:PiliPlus/http/danmaku_block.dart';
|
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:PiliPlus/models/user/danmaku_block.dart';
|
||||||
|
import 'package:crclib/catalog.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class DanmakuBlockController extends GetxController
|
class DanmakuBlockController extends GetxController
|
||||||
with GetSingleTickerProviderStateMixin {
|
with GetSingleTickerProviderStateMixin {
|
||||||
final ruleTypes = RxMap<int, Map<int, String>>({0: {}, 1: {}, 2: {}});
|
late final List<RxList<SimpleRule>> rules =
|
||||||
|
List.generate(DmBlockType.values.length, (_) => <SimpleRule>[].obs);
|
||||||
|
|
||||||
late TabController tabController;
|
late TabController tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
queryDanmakuFilter();
|
||||||
tabController = TabController(length: 3, vsync: this);
|
tabController = TabController(length: 3, vsync: this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,12 +34,9 @@ class DanmakuBlockController extends GetxController
|
|||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
DanmakuBlockDataModel data = result['data'];
|
DanmakuBlockDataModel data = result['data'];
|
||||||
if (data.rule?.isNotEmpty == true) {
|
rules[0].addAll(data.rule);
|
||||||
for (var rule in data.rule!) {
|
rules[1].addAll(data.rule1);
|
||||||
ruleTypes[rule.type]![rule.id] = rule.filter;
|
rules[2].addAll(data.rule2);
|
||||||
}
|
|
||||||
ruleTypes.refresh();
|
|
||||||
}
|
|
||||||
if (data.toast != null) {
|
if (data.toast != null) {
|
||||||
SmartDialog.showToast(data.toast!);
|
SmartDialog.showToast(data.toast!);
|
||||||
}
|
}
|
||||||
@@ -41,29 +45,30 @@ class DanmakuBlockController extends GetxController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> danmakuFilterDel(int type, int id) async {
|
Future<void> danmakuFilterDel(int tabIndex, int itemIndex, int id) async {
|
||||||
SmartDialog.showLoading(msg: '正在删除弹幕屏蔽规则……');
|
SmartDialog.showLoading(msg: '正在删除弹幕屏蔽规则……');
|
||||||
var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id);
|
var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
ruleTypes
|
rules[tabIndex].removeAt(itemIndex);
|
||||||
..[type]!.remove(id)
|
SmartDialog.showToast('删除成功');
|
||||||
..refresh();
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
SmartDialog.showToast(result['msg']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> danmakuFilterAdd(
|
Future<void> danmakuFilterAdd(
|
||||||
{required String filter, required int type}) async {
|
{required String filter, required int type}) async {
|
||||||
|
if (type == 2) {
|
||||||
|
filter = Crc32Xz().convert(utf8.encode(filter)).toRadixString(16);
|
||||||
|
}
|
||||||
SmartDialog.showLoading(msg: '正在添加弹幕屏蔽规则……');
|
SmartDialog.showLoading(msg: '正在添加弹幕屏蔽规则……');
|
||||||
var result =
|
var result =
|
||||||
await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type);
|
await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
SimpleRule rule = result['data'];
|
SimpleRule rule = result['data'];
|
||||||
ruleTypes
|
rules[type].add(rule);
|
||||||
..[type]![rule.id] = rule.filter
|
|
||||||
..refresh();
|
|
||||||
SmartDialog.showToast('添加成功');
|
SmartDialog.showToast('添加成功');
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
|
|||||||
@@ -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/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/models/user/danmaku_rule.dart';
|
||||||
import 'package:PiliPlus/pages/danmaku_block/controller.dart';
|
import 'package:PiliPlus/pages/danmaku_block/controller.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -15,75 +21,130 @@ class DanmakuBlockPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
|
class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
|
||||||
final DanmakuBlockController _danmakuBlockController =
|
final DanmakuBlockController _controller = Get.put(DanmakuBlockController());
|
||||||
Get.put(DanmakuBlockController());
|
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
late PlPlayerController plPlayerController;
|
late PlPlayerController plPlayerController;
|
||||||
|
|
||||||
static const Map<int, String> ruleLabels = {
|
|
||||||
0: '关键词',
|
|
||||||
1: '正则',
|
|
||||||
2: '用户',
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_danmakuBlockController.queryDanmakuFilter();
|
|
||||||
});
|
|
||||||
plPlayerController = Get.arguments as PlPlayerController;
|
plPlayerController = Get.arguments as PlPlayerController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
final ruleFilter = RuleFilter.fromRuleTypeEntires(
|
final ruleFilter = RuleFilter.fromRuleTypeEntires(_controller.rules);
|
||||||
_danmakuBlockController.ruleTypes.entries);
|
|
||||||
plPlayerController.filters = ruleFilter;
|
plPlayerController.filters = ruleFilter;
|
||||||
scrollController.dispose();
|
|
||||||
GStorage.localCache.put(LocalCacheKey.danmakuFilterRules, ruleFilter);
|
GStorage.localCache.put(LocalCacheKey.danmakuFilterRules, ruleFilter);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showAddDialog(int type) {
|
@override
|
||||||
final TextEditingController textController = TextEditingController();
|
Widget build(BuildContext context) {
|
||||||
late String hintText;
|
return Scaffold(
|
||||||
switch (type) {
|
resizeToAvoidBottomInset: false,
|
||||||
case 0:
|
appBar: AppBar(
|
||||||
hintText = '输入过滤的关键词,其它类别请切换标签页后添加';
|
title: const Text('弹幕屏蔽'),
|
||||||
break;
|
bottom: TabBar(
|
||||||
case 1:
|
controller: _controller.tabController,
|
||||||
hintText = '输入//之间的正则表达式,无需包含头尾的"/"';
|
tabs: DmBlockType.values
|
||||||
break;
|
.map((e) => Obx(() => Tab(
|
||||||
case 2:
|
text: '${e.label}(${_controller.rules[e.index].length})')))
|
||||||
hintText = '输入经CRC32B(即小写16进制的CRC32)哈希后的用户UID';
|
.toList(),
|
||||||
break;
|
),
|
||||||
|
),
|
||||||
|
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<SimpleRule> 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(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('添加新的${ruleLabels[type]}规则'),
|
title: Text('添加新的${type.label}规则'),
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
content: Column(
|
||||||
Text(hintText),
|
mainAxisSize: MainAxisSize.min,
|
||||||
TextField(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
controller: textController,
|
children: [
|
||||||
autofocus: true,
|
Text(hintText),
|
||||||
)
|
TextFormField(
|
||||||
]),
|
autofocus: true,
|
||||||
actions: <Widget>[
|
initialValue: filter,
|
||||||
|
onChanged: (value) => filter = value,
|
||||||
|
keyboardType: isUid ? TextInputType.number : null,
|
||||||
|
inputFormatters: isUid
|
||||||
|
? [FilteringTextInputFormatter.allow(RegExp(r'\d+'))]
|
||||||
|
: null,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: Navigator.of(context).pop,
|
onPressed: Get.back,
|
||||||
child: const Text('取消'),
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('添加'),
|
child: const Text('添加'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
String filter = textController.text;
|
|
||||||
if (filter.isNotEmpty) {
|
if (filter.isNotEmpty) {
|
||||||
_danmakuBlockController.danmakuFilterAdd(
|
Get.back();
|
||||||
filter: filter, type: type);
|
_controller.danmakuFilterAdd(
|
||||||
Navigator.of(context).pop();
|
filter: filter, type: type.index);
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast('输入内容不能为空');
|
SmartDialog.showToast('输入内容不能为空');
|
||||||
}
|
}
|
||||||
@@ -94,53 +155,4 @@ class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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<MapEntry<int, String>> 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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user