block tags and authors

This commit is contained in:
wgh19
2024-05-22 20:40:35 +08:00
parent de26cba0fa
commit 7641cc8f5c
8 changed files with 500 additions and 118 deletions

15
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"cSpell.words": [
"appdata",
"Bungo",
"gjzr",
"microtask",
"mypixiv",
"pawoo",
"Rorigod",
"sleepinglife",
"Ugoira",
"vocaloidhm",
"vsync"
]
}

View File

@@ -64,9 +64,9 @@
"Weekly Manga": "每周漫画", "Weekly Manga": "每周漫画",
"Monthly Manga": "每月漫画", "Monthly Manga": "每月漫画",
"R18": "R18", "R18": "R18",
"Account": "账", "Account": "账",
"Logout": "登出", "Logout": "登出",
"Account Settings": "账设置", "Account Settings": "账设置",
"Edit": "编辑", "Edit": "编辑",
"Download": "下载", "Download": "下载",
"Manage": "管理", "Manage": "管理",
@@ -138,7 +138,16 @@
"Line Height": "行高", "Line Height": "行高",
"Paragraph Spacing": "段间距", "Paragraph Spacing": "段间距",
"light": "浅色", "light": "浅色",
"dark": "深色" "dark": "深色",
"block": "屏蔽",
"Block": "屏蔽",
"Block(Account)": "屏蔽(账号)",
"Block(Local)": "屏蔽(本地)",
"Add": "添加",
"Submit": "提交",
"Local": "本地",
"Both": "同时",
"This artwork is blocked": "此作品已被屏蔽"
}, },
"zh_TW": { "zh_TW": {
"Search": "搜索", "Search": "搜索",
@@ -279,6 +288,15 @@
"Line Height": "行高", "Line Height": "行高",
"Paragraph Spacing": "段間距", "Paragraph Spacing": "段間距",
"light": "淺色", "light": "淺色",
"dark": "深色" "dark": "深色",
"block": "屏蔽",
"Block": "屏蔽",
"Block(Account)": "屏蔽(賬戶)",
"Block(Local)": "屏蔽(本地)",
"Add": "添加",
"Submit": "提交",
"Local": "本地",
"Both": "同時",
"This artwork is blocked": "此作品已被屏蔽"
} }
} }

View File

@@ -527,3 +527,25 @@ class Novel {
commentsCount = json["total_comments"], commentsCount = json["total_comments"],
isAi = json["novel_ai_type"] == 2; isAi = json["novel_ai_type"] == 2;
} }
class MuteList {
List<Tag> tags;
List<Author> authors;
int limit;
MuteList(this.tags, this.authors, this.limit);
static MuteList? fromJson(Map<String, dynamic> data) {
return MuteList(
(data['muted_tags'] as List)
.map((e) => Tag(e['tag'], e['tag_translation']))
.toList(),
(data['muted_users'] as List)
.map((e) => Author(e['user_id'], e['user_name'], e['user_account'],
e['user_profile_image_urls']['medium'], false))
.toList(),
data['mute_limit_count']);
}
}

View File

@@ -191,6 +191,21 @@ class Network {
} }
} }
String? encodeFormData(Map<String, dynamic>? data) {
if (data == null) return null;
StringBuffer buffer = StringBuffer();
data.forEach((key, value) {
if (value is List) {
for (var element in value) {
buffer.write("$key[]=$element&");
}
} else {
buffer.write("$key=$value&");
}
});
return buffer.toString();
}
Future<Res<Map<String, dynamic>>> apiPost(String path, Future<Res<Map<String, dynamic>>> apiPost(String path,
{Map<String, dynamic>? query, Map<String, dynamic>? data}) async { {Map<String, dynamic>? query, Map<String, dynamic>? data}) async {
try { try {
@@ -199,7 +214,7 @@ class Network {
} }
final res = await dio.post<Map<String, dynamic>>(path, final res = await dio.post<Map<String, dynamic>>(path,
queryParameters: query, queryParameters: query,
data: data, data: encodeFormData(data),
options: Options( options: Options(
headers: headers, headers: headers,
validateStatus: (status) => true, validateStatus: (status) => true,
@@ -497,21 +512,24 @@ class Network {
} }
} }
Future<List<Tag>> getMutedTags() async { Future<Res<MuteList>> getMuteList() async {
var res = await apiGet("/v1/mute/list"); var res = await apiGet("/v1/mute/list");
if (res.success) { if (res.success) {
return res.data["mute_tags"] return Res(MuteList.fromJson(res.data));
.map<Tag>((e) => Tag(e["tag"]["name"], e["tag"]["translated_name"]))
.toList();
} else { } else {
return []; return Res.error(res.errorMessage);
} }
} }
Future<Res<bool>> muteTags( Future<Res<bool>> editMute(List<String> addTags, List<String> addUsers,
List<String> muteTags, List<String> unmuteTags) async { List<String> deleteTags, List<String> deleteUsers) async {
var res = await apiPost("/v1/mute/edit", var res = await apiPost("/v1/mute/edit",
data: {"add_tags": muteTags, "delete_tags": unmuteTags}); data: {
"add_tags": addTags,
"add_user_ids": addUsers,
"delete_tags": deleteTags,
"delete_user_ids": deleteUsers
}..removeWhere((key, value) => value.isEmpty));
if (res.success) { if (res.success) {
return const Res(true); return const Res(true);
} else { } else {

View File

@@ -5,9 +5,11 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show Icons; import 'package:flutter/material.dart' show Icons;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/components/animated_image.dart'; import 'package:pixes/components/animated_image.dart';
import 'package:pixes/components/loading.dart'; import 'package:pixes/components/loading.dart';
import 'package:pixes/components/message.dart'; import 'package:pixes/components/message.dart';
import 'package:pixes/components/page_route.dart';
import 'package:pixes/components/title_bar.dart'; import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/image_provider.dart'; import 'package:pixes/foundation/image_provider.dart';
@@ -17,6 +19,7 @@ import 'package:pixes/pages/comments_page.dart';
import 'package:pixes/pages/image_page.dart'; import 'package:pixes/pages/image_page.dart';
import 'package:pixes/pages/search_page.dart'; import 'package:pixes/pages/search_page.dart';
import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/pages/user_info_page.dart';
import 'package:pixes/utils/block.dart';
import 'package:pixes/utils/translation.dart'; import 'package:pixes/utils/translation.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
@@ -143,6 +146,7 @@ class IllustPage extends StatefulWidget {
class _IllustPageState extends State<IllustPage> { class _IllustPageState extends State<IllustPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var isBlocked = checkIllusts([widget.illust]).isEmpty;
return buildKeyboardListener(ColoredBox( return buildKeyboardListener(ColoredBox(
color: FluentTheme.of(context).micaBackgroundColor, color: FluentTheme.of(context).micaBackgroundColor,
child: SizedBox.expand( child: SizedBox.expand(
@@ -151,6 +155,7 @@ class _IllustPageState extends State<IllustPage> {
child: LayoutBuilder(builder: (context, constrains) { child: LayoutBuilder(builder: (context, constrains) {
return Stack( return Stack(
children: [ children: [
if (!isBlocked)
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@@ -158,12 +163,22 @@ class _IllustPageState extends State<IllustPage> {
top: 0, top: 0,
child: buildBody(constrains.maxWidth, constrains.maxHeight), child: buildBody(constrains.maxWidth, constrains.maxHeight),
), ),
if (!isBlocked)
_BottomBar( _BottomBar(
widget.illust, widget.illust,
constrains.maxHeight, constrains.maxHeight,
constrains.maxWidth, constrains.maxWidth,
favoriteCallback: widget.favoriteCallback, favoriteCallback: widget.favoriteCallback,
updateCallback: () => setState(() {}),
), ),
if (isBlocked)
const Positioned.fill(
child: Center(
child: Center(
child: Text(
"This artwork is blocked",
)),
))
], ],
); );
}), }),
@@ -306,10 +321,12 @@ class _IllustPageState extends State<IllustPage> {
class _BottomBar extends StatefulWidget { class _BottomBar extends StatefulWidget {
const _BottomBar(this.illust, this.height, this.width, const _BottomBar(this.illust, this.height, this.width,
{this.favoriteCallback}); {this.favoriteCallback, this.updateCallback});
final void Function(bool)? favoriteCallback; final void Function(bool)? favoriteCallback;
final void Function()? updateCallback;
final Illust illust; final Illust illust;
final double height; final double height;
@@ -378,8 +395,9 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
} }
void _handlePointerCancel() { void _handlePointerCancel() {
if (animationController.value == 1 || animationController.value == 0) if (animationController.value == 1 || animationController.value == 0) {
return; return;
}
if (animationController.value >= 0.5) { if (animationController.value >= 0.5) {
animationController.forward(); animationController.forward();
} else { } else {
@@ -876,7 +894,9 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
} }
Widget buildMoreActions() { Widget buildMoreActions() {
return Row( return Wrap(
runSpacing: 4,
spacing: 8,
children: [ children: [
Button( Button(
onPressed: () => favorite("private"), onPressed: () => favorite("private"),
@@ -913,10 +933,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
], ],
), ),
), ),
), ).fixWidth(96),
const SizedBox(
width: 6,
),
Button( Button(
onPressed: () { onPressed: () {
Share.share( Share.share(
@@ -937,10 +954,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
], ],
), ),
), ),
), ).fixWidth(96),
const SizedBox(
width: 6,
),
Button( Button(
onPressed: () { onPressed: () {
var text = "https://pixiv.net/artworks/${widget.illust.id}"; var text = "https://pixiv.net/artworks/${widget.illust.id}";
@@ -959,10 +973,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
], ],
), ),
), ),
), ).fixWidth(96),
const SizedBox(
width: 6,
),
Button( Button(
onPressed: () { onPressed: () {
context.to(() => _RelatedIllustsPage(widget.illust.id.toString())); context.to(() => _RelatedIllustsPage(widget.illust.id.toString()));
@@ -979,12 +990,189 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin {
], ],
), ),
), ),
).fixWidth(96),
Button(
onPressed: () async {
await Navigator.of(context)
.push(SideBarRoute(_BlockingPage(widget.illust)));
if (mounted) {
widget.updateCallback?.call();
}
},
child: SizedBox(
height: 28,
child: Row(
children: [
const Icon(MdIcons.block, size: 18),
const SizedBox(
width: 8,
), ),
Text("Block".tl)
],
),
),
).fixWidth(96),
], ],
).paddingHorizontal(2).paddingBottom(4); ).paddingHorizontal(2).paddingBottom(4);
} }
} }
class _BlockingPage extends StatefulWidget {
const _BlockingPage(this.illust);
final Illust illust;
@override
State<_BlockingPage> createState() => __BlockingPageState();
}
class __BlockingPageState extends State<_BlockingPage> {
List<int> blockedTags = [];
@override
Widget build(BuildContext context) {
return Column(
children: [
TitleBar(title: "Block".tl),
Expanded(
child: ListView.builder(
padding: EdgeInsets.only(bottom: context.padding.bottom),
itemCount: widget.illust.tags.length + 2,
itemBuilder: (context, index) {
if (index == widget.illust.tags.length + 1) {
return buildSubmit();
}
var text = index == 0
? widget.illust.author.name
: widget.illust.tags[index - 1].name;
var subTitle = index == 0
? "author"
: widget.illust.tags[index - 1].translatedName ?? "";
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
borderColor: blockedTags.contains(index)
? ColorScheme.of(context).outlineVariant
: ColorScheme.of(context).outlineVariant.withOpacity(0.2),
padding: EdgeInsets.zero,
child: ListTile(
title: Text(text),
subtitle: Text(subTitle),
trailing: Button(
onPressed: () {
if (blockedTags.contains(index)) {
blockedTags.remove(index);
} else {
blockedTags.add(index);
}
setState(() {});
},
child: blockedTags.contains(index)
? Text("Cancel".tl)
: Text("Block".tl))
.fixWidth(72),
),
);
},
),
)
],
);
}
var flyout = FlyoutController();
bool isSubmitting = false;
Widget buildSubmit() {
return FlyoutTarget(
controller: flyout,
child: FilledButton(
onPressed: () async {
if (this.blockedTags.isEmpty) {
return;
}
if (isSubmitting) return;
var blockedTags = <String>[];
var blockedUsers = <String>[];
for (var i in this.blockedTags) {
if (i == 0) {
blockedUsers.add(widget.illust.author.id.toString());
} else {
blockedTags.add(widget.illust.tags[i - 1].name);
}
}
bool addToAccount = false;
bool addToLocal = false;
if (appdata.account!.user.isPremium) {
await flyout.showFlyout(
navigatorKey: App.rootNavigatorKey.currentState,
builder: (context) {
return MenuFlyout(
items: [
MenuFlyoutItem(
text: Text("Local".tl),
onPressed: () {
addToLocal = true;
}),
MenuFlyoutItem(
text: Text("Account".tl),
onPressed: () {
addToAccount = true;
}),
MenuFlyoutItem(
text: Text("Both".tl),
onPressed: () {
addToLocal = true;
addToAccount = true;
}),
],
);
});
} else {
addToLocal = true;
}
if (addToAccount) {
setState(() {
isSubmitting = true;
});
var res =
await Network().editMute(blockedTags, blockedUsers, [], []);
setState(() {
isSubmitting = false;
});
if (res.error) {
if (mounted) {
context.showToast(message: "Network Error");
}
return;
}
}
if (addToLocal) {
for (var tag in blockedTags) {
appdata.settings['blockTags'].add(tag);
}
for (var user in blockedUsers) {
appdata.settings['blockTags'].add('user:$user');
}
appdata.writeSettings();
}
if (mounted) {
context.pop();
}
},
child: isSubmitting
? const ProgressRing(
strokeWidth: 1.6,
).fixWidth(18).fixHeight(18).toAlign(Alignment.center)
: Text("Submit".tl),
).fixWidth(96).fixHeight(28),
).toAlign(Alignment.center).paddingTop(16);
}
}
class IllustPageWithId extends StatefulWidget { class IllustPageWithId extends StatefulWidget {
const IllustPageWithId(this.id, {super.key}); const IllustPageWithId(this.id, {super.key});

View File

@@ -2,7 +2,6 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:pixes/appdata.dart'; import 'package:pixes/appdata.dart';
import 'package:pixes/components/loading.dart'; import 'package:pixes/components/loading.dart';
import 'package:pixes/components/message.dart';
import 'package:pixes/components/novel.dart'; import 'package:pixes/components/novel.dart';
import 'package:pixes/components/page_route.dart'; import 'package:pixes/components/page_route.dart';
import 'package:pixes/components/user_preview.dart'; import 'package:pixes/components/user_preview.dart';
@@ -128,7 +127,6 @@ class _SearchPageState extends State<SearchPage> {
), ),
onPressed: () { onPressed: () {
optionController.showFlyout( optionController.showFlyout(
navigatorKey: App.rootNavigatorKey.currentState,
placementMode: FlyoutPlacementMode.bottomCenter, placementMode: FlyoutPlacementMode.bottomCenter,
builder: buildSearchOption, builder: buildSearchOption,
); );

View File

@@ -182,23 +182,30 @@ class _SettingsPageState extends State<SettingsPage> {
buildItem( buildItem(
title: "Github", title: "Github",
action: IconButton( action: IconButton(
icon: const Icon(MdIcons.open_in_new, size: 18,), icon: const Icon(
MdIcons.open_in_new,
size: 18,
),
onPressed: () => onPressed: () =>
launchUrlString("https://github.com/wgh136/pixes"), launchUrlString("https://github.com/wgh136/pixes"),
)), )),
buildItem( buildItem(
title: "Telegram", title: "Telegram",
action: IconButton( action: IconButton(
icon: const Icon(MdIcons.open_in_new, size: 18,), icon: const Icon(
onPressed: () => MdIcons.open_in_new,
launchUrlString("https://t.me/pica_group"), size: 18,
),
onPressed: () => launchUrlString("https://t.me/pica_group"),
)), )),
buildItem( buildItem(
title: "Logs", title: "Logs",
action: IconButton( action: IconButton(
icon: const Icon(MdIcons.open_in_new, size: 18,), icon: const Icon(
onPressed: () => context.to(() => const LogsPage()) MdIcons.open_in_new,
)), size: 18,
),
onPressed: () => context.to(() => const LogsPage()))),
], ],
), ),
); );
@@ -219,6 +226,22 @@ class _SettingsPageState extends State<SettingsPage> {
)); ));
}, },
)), )),
buildItem(
title: "Block(Account)".tl,
action: Button(
child: Text("Edit".tl).fixWidth(64),
onPressed: () {
launchUrlString("https://www.pixiv.net/setting_mute.php");
},
)),
buildItem(
title: "Block(Local)".tl,
action: Button(
child: Text("Edit".tl).fixWidth(64),
onPressed: () {
context.to(() => const _BlockTagsPage());
},
)),
], ],
), ),
); );
@@ -233,21 +256,27 @@ class _SettingsPageState extends State<SettingsPage> {
action: DropDownButton( action: DropDownButton(
title: Text(appdata.settings["theme"] ?? "System".tl), title: Text(appdata.settings["theme"] ?? "System".tl),
items: [ items: [
MenuFlyoutItem(text: Text("System".tl), onPressed: () { MenuFlyoutItem(
text: Text("System".tl),
onPressed: () {
setState(() { setState(() {
appdata.settings["theme"] = "System"; appdata.settings["theme"] = "System";
}); });
appdata.writeData(); appdata.writeData();
StateController.findOrNull(tag: "MyApp")?.update(); StateController.findOrNull(tag: "MyApp")?.update();
}), }),
MenuFlyoutItem(text: Text("light".tl), onPressed: () { MenuFlyoutItem(
text: Text("light".tl),
onPressed: () {
setState(() { setState(() {
appdata.settings["theme"] = "Light"; appdata.settings["theme"] = "Light";
}); });
appdata.writeData(); appdata.writeData();
StateController.findOrNull(tag: "MyApp")?.update(); StateController.findOrNull(tag: "MyApp")?.update();
}), }),
MenuFlyoutItem(text: Text("dark".tl), onPressed: () { MenuFlyoutItem(
text: Text("dark".tl),
onPressed: () {
setState(() { setState(() {
appdata.settings["theme"] = "Dark"; appdata.settings["theme"] = "Dark";
}); });
@@ -260,28 +289,36 @@ class _SettingsPageState extends State<SettingsPage> {
action: DropDownButton( action: DropDownButton(
title: Text(appdata.settings["language"] ?? "System"), title: Text(appdata.settings["language"] ?? "System"),
items: [ items: [
MenuFlyoutItem(text: const Text("System"), onPressed: () { MenuFlyoutItem(
text: const Text("System"),
onPressed: () {
setState(() { setState(() {
appdata.settings["language"] = "System"; appdata.settings["language"] = "System";
}); });
appdata.writeData(); appdata.writeData();
StateController.findOrNull(tag: "MyApp")?.update(); StateController.findOrNull(tag: "MyApp")?.update();
}), }),
MenuFlyoutItem(text: const Text("English"), onPressed: () { MenuFlyoutItem(
text: const Text("English"),
onPressed: () {
setState(() { setState(() {
appdata.settings["language"] = "English"; appdata.settings["language"] = "English";
}); });
appdata.writeData(); appdata.writeData();
StateController.findOrNull(tag: "MyApp")?.update(); StateController.findOrNull(tag: "MyApp")?.update();
}), }),
MenuFlyoutItem(text: const Text("简体中文"), onPressed: () { MenuFlyoutItem(
text: const Text("简体中文"),
onPressed: () {
setState(() { setState(() {
appdata.settings["language"] = "简体中文"; appdata.settings["language"] = "简体中文";
}); });
appdata.writeData(); appdata.writeData();
StateController.findOrNull(tag: "MyApp")?.update(); StateController.findOrNull(tag: "MyApp")?.update();
}), }),
MenuFlyoutItem(text: const Text("繁體中文"), onPressed: () { MenuFlyoutItem(
text: const Text("繁體中文"),
onPressed: () {
setState(() { setState(() {
appdata.settings["language"] = "繁體中文"; appdata.settings["language"] = "繁體中文";
}); });
@@ -416,3 +453,88 @@ ${"Some keywords will be replaced by the following rule:".tl}
${"Multiple path separators will be automatically replaced with a single".tl} ${"Multiple path separators will be automatically replaced with a single".tl}
"""; """;
} }
class _BlockTagsPage extends StatefulWidget {
const _BlockTagsPage();
@override
State<_BlockTagsPage> createState() => __BlockTagsPageState();
}
class __BlockTagsPageState extends State<_BlockTagsPage> {
@override
Widget build(BuildContext context) {
return Column(
children: [
TitleBar(
title: "Block".tl,
action: FilledButton(
child: Text("Add".tl),
onPressed: () {
var controller = TextEditingController();
void finish(BuildContext context) {
var text = controller.text;
if (text.isNotEmpty &&
!(appdata.settings["blockTags"] as List).contains(text)) {
setState(() {
appdata.settings["blockTags"].add(text);
});
appdata.writeSettings();
}
context.pop();
}
showDialog(
context: context,
barrierDismissible: true,
builder: (context) {
return ContentDialog(
title: Text("Add".tl),
content: SizedBox(
width: 300,
height: 32,
child: TextBox(
controller: controller,
onSubmitted: (v) => finish(context),
),
),
actions: [
FilledButton(
child: Text("Submit".tl),
onPressed: () {
finish(context);
})
],
);
});
},
),
),
Expanded(
child: ListView.builder(
itemCount: appdata.settings["blockTags"].length,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: EdgeInsets.zero,
child: ListTile(
title: Text(appdata.settings["blockTags"][index]),
trailing: Button(
child: Text("Delete".tl),
onPressed: () {
setState(() {
(appdata.settings["blockTags"] as List).removeAt(index);
});
appdata.writeSettings();
},
),
),
);
},
),
)
],
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:pixes/appdata.dart'; import 'package:pixes/appdata.dart';
import 'package:pixes/network/models.dart'; import 'package:pixes/network/models.dart';
void checkIllusts(List<Illust> illusts) { List<Illust> checkIllusts(List<Illust> illusts) {
illusts.removeWhere((illust) { illusts.removeWhere((illust) {
if (illust.isBlocked) { if (illust.isBlocked) {
return true; return true;
@@ -9,7 +9,7 @@ void checkIllusts(List<Illust> illusts) {
if (appdata.settings["blockTags"] == null) { if (appdata.settings["blockTags"] == null) {
return false; return false;
} }
if (appdata.settings["blockTags"].contains(illust.author.name)) { if (appdata.settings["blockTags"].contains("user:${illust.author.name}")) {
return true; return true;
} }
for (var tag in illust.tags) { for (var tag in illust.tags) {
@@ -19,4 +19,5 @@ void checkIllusts(List<Illust> illusts) {
} }
return false; return false;
}); });
return illusts;
} }