Compare commits

..

3 Commits

Author SHA1 Message Date
c50d85a6a1 feat: implement comment blocking based on user-defined keywords 2025-11-29 14:57:00 +08:00
38b5947c0a feat: add comment keyword blocking functionality 2025-11-29 14:52:24 +08:00
053293839e flutter 3.38.3 2025-11-29 14:43:15 +08:00
6 changed files with 131 additions and 5 deletions

View File

@@ -103,6 +103,7 @@
"Show favorite status on comic tile": "在漫画缩略图上显示收藏状态", "Show favorite status on comic tile": "在漫画缩略图上显示收藏状态",
"Show history on comic tile": "在漫画缩略图上显示历史记录", "Show history on comic tile": "在漫画缩略图上显示历史记录",
"Keyword blocking": "关键词屏蔽", "Keyword blocking": "关键词屏蔽",
"Comment keyword blocking": "评论关键词屏蔽",
"Tap to turn Pages": "点击翻页", "Tap to turn Pages": "点击翻页",
"Page animation": "页面动画", "Page animation": "页面动画",
"Reading mode": "阅读模式", "Reading mode": "阅读模式",
@@ -529,6 +530,7 @@
"Show favorite status on comic tile": "在漫畫縮圖上顯示收藏狀態", "Show favorite status on comic tile": "在漫畫縮圖上顯示收藏狀態",
"Show history on comic tile": "在漫畫縮圖上顯示歷史記錄", "Show history on comic tile": "在漫畫縮圖上顯示歷史記錄",
"Keyword blocking": "關鍵字封鎖", "Keyword blocking": "關鍵字封鎖",
"Comment keyword blocking": "評論關鍵字封鎖",
"Tap to turn Pages": "點擊翻頁", "Tap to turn Pages": "點擊翻頁",
"Page animation": "頁面動畫", "Page animation": "頁面動畫",
"Reading mode": "閱讀模式", "Reading mode": "閱讀模式",

View File

@@ -180,6 +180,7 @@ class Settings with ChangeNotifier {
'showFavoriteStatusOnTile': true, 'showFavoriteStatusOnTile': true,
'showHistoryStatusOnTile': false, 'showHistoryStatusOnTile': false,
'blockedWords': [], 'blockedWords': [],
'blockedCommentWords': [],
'defaultSearchTarget': null, 'defaultSearchTarget': null,
'autoPageTurningInterval': 5, // in seconds 'autoPageTurningInterval': 5, // in seconds
'readerMode': 'galleryLeftToRight', // values of [ReaderMode] 'readerMode': 'galleryLeftToRight', // values of [ReaderMode]

View File

@@ -1,5 +1,18 @@
part of 'comic_page.dart'; part of 'comic_page.dart';
bool _shouldBlockComment(Comment comment) {
var blockedWords = appdata.settings["blockedCommentWords"] as List;
if (blockedWords.isEmpty) return false;
var content = comment.content.toLowerCase();
for (var word in blockedWords) {
if (content.contains(word.toString().toLowerCase())) {
return true;
}
}
return false;
}
class CommentsPage extends StatefulWidget { class CommentsPage extends StatefulWidget {
const CommentsPage({ const CommentsPage({
super.key, super.key,
@@ -36,8 +49,9 @@ class _CommentsPageState extends State<CommentsPage> {
_loading = false; _loading = false;
}); });
} else if (mounted) { } else if (mounted) {
var filteredComments = res.data.where((c) => !_shouldBlockComment(c)).toList();
setState(() { setState(() {
_comments = res.data; _comments = filteredComments;
_loading = false; _loading = false;
maxPage = res.subData; maxPage = res.subData;
}); });
@@ -54,8 +68,9 @@ class _CommentsPageState extends State<CommentsPage> {
if (res.error) { if (res.error) {
context.showMessage(message: res.errorMessage ?? "Unknown Error"); context.showMessage(message: res.errorMessage ?? "Unknown Error");
} else { } else {
var filteredComments = res.data.where((c) => !_shouldBlockComment(c)).toList();
setState(() { setState(() {
_comments!.addAll(res.data); _comments!.addAll(filteredComments);
_page++; _page++;
if (maxPage == null && res.data.isEmpty) { if (maxPage == null && res.data.isEmpty) {
maxPage = _page; maxPage = _page;

View File

@@ -21,7 +21,7 @@ class _CommentsPartState extends State<_CommentsPart> {
@override @override
void initState() { void initState() {
comments = widget.comments; comments = widget.comments.where((c) => !_shouldBlockComment(c)).toList();
super.initState(); super.initState();
} }

View File

@@ -1,5 +1,18 @@
part of 'reader.dart'; part of 'reader.dart';
bool _shouldBlockComment(Comment comment) {
var blockedWords = appdata.settings["blockedCommentWords"] as List;
if (blockedWords.isEmpty) return false;
var content = comment.content.toLowerCase();
for (var word in blockedWords) {
if (content.contains(word.toString().toLowerCase())) {
return true;
}
}
return false;
}
class ChapterCommentsPage extends StatefulWidget { class ChapterCommentsPage extends StatefulWidget {
const ChapterCommentsPage({ const ChapterCommentsPage({
super.key, super.key,
@@ -44,8 +57,9 @@ class _ChapterCommentsPageState extends State<ChapterCommentsPage> {
_loading = false; _loading = false;
}); });
} else if (mounted) { } else if (mounted) {
var filteredComments = res.data.where((c) => !_shouldBlockComment(c)).toList();
setState(() { setState(() {
_comments = res.data; _comments = filteredComments;
_loading = false; _loading = false;
maxPage = res.subData; maxPage = res.subData;
}); });
@@ -62,8 +76,9 @@ class _ChapterCommentsPageState extends State<ChapterCommentsPage> {
if (res.error) { if (res.error) {
context.showMessage(message: res.errorMessage ?? "Unknown Error"); context.showMessage(message: res.errorMessage ?? "Unknown Error");
} else { } else {
var filteredComments = res.data.where((c) => !_shouldBlockComment(c)).toList();
setState(() { setState(() {
_comments!.addAll(res.data); _comments!.addAll(filteredComments);
_page++; _page++;
if (maxPage == null && res.data.isEmpty) { if (maxPage == null && res.data.isEmpty) {
maxPage = _page; maxPage = _page;

View File

@@ -60,6 +60,10 @@ class _ExploreSettingsState extends State<ExploreSettings> {
title: "Keyword blocking".tl, title: "Keyword blocking".tl,
builder: () => const _ManageBlockingWordView(), builder: () => const _ManageBlockingWordView(),
).toSliver(), ).toSliver(),
_PopupWindowSetting(
title: "Comment keyword blocking".tl,
builder: () => const _ManageBlockingCommentWordView(),
).toSliver(),
SelectSetting( SelectSetting(
title: "Default Search Target".tl, title: "Default Search Target".tl,
settingKey: "defaultSearchTarget", settingKey: "defaultSearchTarget",
@@ -250,4 +254,93 @@ Widget setSearchSourcesWidget() {
settingsIndex: "searchSources", settingsIndex: "searchSources",
pages: pages, pages: pages,
); );
}
class _ManageBlockingCommentWordView extends StatefulWidget {
const _ManageBlockingCommentWordView();
@override
State<_ManageBlockingCommentWordView> createState() =>
_ManageBlockingCommentWordViewState();
}
class _ManageBlockingCommentWordViewState extends State<_ManageBlockingCommentWordView> {
@override
Widget build(BuildContext context) {
assert(appdata.settings["blockedCommentWords"] is List);
return PopUpWidgetScaffold(
title: "Comment keyword blocking".tl,
tailing: [
TextButton.icon(
icon: const Icon(Icons.add),
label: Text("Add".tl),
onPressed: add,
),
],
body: ListView.builder(
itemCount: appdata.settings["blockedCommentWords"].length,
itemBuilder: (context, index) {
return ListTile(
title: Text(appdata.settings["blockedCommentWords"][index]),
trailing: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
appdata.settings["blockedCommentWords"].removeAt(index);
appdata.saveData();
setState(() {});
},
),
);
},
),
);
}
void add() {
showDialog(
context: App.rootContext,
builder: (context) {
var controller = TextEditingController();
String? error;
return StatefulBuilder(builder: (context, setState) {
return ContentDialog(
title: "Add keyword".tl,
content: TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
label: Text("Keyword".tl),
errorText: error,
),
onChanged: (s) {
if (error != null) {
setState(() {
error = null;
});
}
},
).paddingHorizontal(12),
actions: [
Button.filled(
onPressed: () {
if (appdata.settings["blockedCommentWords"]
.contains(controller.text)) {
setState(() {
error = "Keyword already exists".tl;
});
return;
}
appdata.settings["blockedCommentWords"].add(controller.text);
appdata.saveData();
this.setState(() {});
context.pop();
},
child: Text("Add".tl),
),
],
);
});
},
);
}
} }