mirror of
https://github.com/wgh136/pixes.git
synced 2025-09-27 12:57:24 +00:00
novel reading settings; improve ui
This commit is contained in:
@@ -132,7 +132,13 @@
|
|||||||
"Replace with '-p${index}' if the work have more than one images, otherwise replace with blank.": "替换为'-p${index}'如果作品有多张图片, 否则替换为空白",
|
"Replace with '-p${index}' if the work have more than one images, otherwise replace with blank.": "替换为'-p${index}'如果作品有多张图片, 否则替换为空白",
|
||||||
"Recommendation": "推荐",
|
"Recommendation": "推荐",
|
||||||
"Novel": "小说",
|
"Novel": "小说",
|
||||||
"Novels": "小说"
|
"Novels": "小说",
|
||||||
|
"Reading Settings": "阅读设置",
|
||||||
|
"Font Size": "字体大小",
|
||||||
|
"Line Height": "行高",
|
||||||
|
"Paragraph Spacing": "段间距",
|
||||||
|
"light": "浅色",
|
||||||
|
"dark": "深色"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Search": "搜索",
|
"Search": "搜索",
|
||||||
@@ -267,6 +273,12 @@
|
|||||||
"Replace with '-p${index}' if the work have more than one images, otherwise replace with blank.": "替換為'-p${index}'如果作品有多張圖片, 否則替換為空白",
|
"Replace with '-p${index}' if the work have more than one images, otherwise replace with blank.": "替換為'-p${index}'如果作品有多張圖片, 否則替換為空白",
|
||||||
"Recommendation": "推薦",
|
"Recommendation": "推薦",
|
||||||
"Novel": "小說",
|
"Novel": "小說",
|
||||||
"Novels": "小說"
|
"Novels": "小說",
|
||||||
|
"Reading Settings": "閱讀設置",
|
||||||
|
"Font Size": "字體大小",
|
||||||
|
"Line Height": "行高",
|
||||||
|
"Paragraph Spacing": "段間距",
|
||||||
|
"light": "淺色",
|
||||||
|
"dark": "深色"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -20,6 +20,9 @@ class _Appdata {
|
|||||||
"proxy": "",
|
"proxy": "",
|
||||||
"darkMode": "System",
|
"darkMode": "System",
|
||||||
"language": "System",
|
"language": "System",
|
||||||
|
"readingFontSize": 16.0,
|
||||||
|
"readingLineHeight": 1.5,
|
||||||
|
"readingParagraphSpacing": 8.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
bool lock = false;
|
bool lock = false;
|
||||||
|
@@ -8,7 +8,6 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.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';
|
||||||
|
@@ -30,6 +30,32 @@ import "downloading_page.dart";
|
|||||||
|
|
||||||
double get _appBarHeight => App.isDesktop ? 36.0 : 48.0;
|
double get _appBarHeight => App.isDesktop ? 36.0 : 48.0;
|
||||||
|
|
||||||
|
class TitleBarAction {
|
||||||
|
final IconData icon;
|
||||||
|
final String title;
|
||||||
|
final void Function() onPressed;
|
||||||
|
|
||||||
|
TitleBarAction(this.icon, this.title, this.onPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TitleBarController extends StateController {
|
||||||
|
TitleBarController();
|
||||||
|
|
||||||
|
final List<TitleBarAction> actions = [
|
||||||
|
if (kDebugMode) TitleBarAction(MdIcons.bug_report, "Debug", debug)
|
||||||
|
];
|
||||||
|
|
||||||
|
void addAction(TitleBarAction action) {
|
||||||
|
actions.add(action);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAction(TitleBarAction action) {
|
||||||
|
actions.remove(action);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MainPage extends StatefulWidget {
|
class MainPage extends StatefulWidget {
|
||||||
const MainPage({super.key});
|
const MainPage({super.key});
|
||||||
|
|
||||||
@@ -46,6 +72,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
StateController.put<TitleBarController>(TitleBarController());
|
||||||
windowManager.addListener(this);
|
windowManager.addListener(this);
|
||||||
listenMouseSideButtonToBack(navigatorKey);
|
listenMouseSideButtonToBack(navigatorKey);
|
||||||
App.mainNavigatorKey = navigatorKey;
|
App.mainNavigatorKey = navigatorKey;
|
||||||
@@ -54,6 +81,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
StateController.remove<TitleBarController>();
|
||||||
windowManager.removeListener(this);
|
windowManager.removeListener(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -224,33 +252,50 @@ class _MainPageState extends State<MainPage> with WindowListener {
|
|||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
height: _appBarHeight,
|
height: _appBarHeight,
|
||||||
title: () {
|
title: () {
|
||||||
if (!App.isDesktop) {
|
return StateBuilder<TitleBarController>(
|
||||||
return const Align(
|
builder: (controller) {
|
||||||
alignment: AlignmentDirectional.centerStart,
|
Widget content = Padding(
|
||||||
child: Text("pixes"),
|
padding: const EdgeInsets.only(bottom: 4),
|
||||||
);
|
|
||||||
}
|
|
||||||
return const DragToMoveArea(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: 4),
|
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
if (!App.isDesktop)
|
||||||
|
const Text(
|
||||||
"Pixes",
|
"Pixes",
|
||||||
style: TextStyle(fontSize: 13),
|
style: TextStyle(fontSize: 13),
|
||||||
),
|
),
|
||||||
Spacer(),
|
if (!App.isDesktop) const Spacer(),
|
||||||
if (kDebugMode)
|
if (App.isDesktop)
|
||||||
Padding(
|
const Expanded(
|
||||||
padding: EdgeInsets.only(right: 138),
|
child: DragToMoveArea(
|
||||||
child: Button(onPressed: debug, child: Text("Debug")),
|
child: Text(
|
||||||
)
|
"Pixes",
|
||||||
|
style: TextStyle(fontSize: 13),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
for (var action in controller.actions)
|
||||||
|
Button(
|
||||||
|
onPressed: action.onPressed,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
action.icon,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(action.title),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).paddingTop(4).paddingLeft(4),
|
||||||
|
if (App.isDesktop) const SizedBox(width: 128),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}(),
|
}(),
|
||||||
leading: _BackButton(navigatorKey),
|
leading: _BackButton(navigatorKey),
|
||||||
|
@@ -53,7 +53,10 @@ class _NovelPageState extends State<NovelPage> {
|
|||||||
),
|
),
|
||||||
if (widget.novel.seriesId != null)
|
if (widget.novel.seriesId != null)
|
||||||
NovelSeriesWidget(
|
NovelSeriesWidget(
|
||||||
widget.novel.seriesId!, widget.novel.seriesTitle!)
|
widget.novel.seriesId!, widget.novel.seriesTitle!),
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 16 + MediaQuery.of(context).padding.bottom)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
).padding(const EdgeInsets.symmetric(horizontal: 16)));
|
).padding(const EdgeInsets.symmetric(horizontal: 16)));
|
||||||
@@ -240,6 +243,7 @@ class _NovelPageState extends State<NovelPage> {
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(widget.novel.author.name,
|
Text(widget.novel.author.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -248,9 +252,9 @@ class _NovelPageState extends State<NovelPage> {
|
|||||||
)),
|
)),
|
||||||
Text(
|
Text(
|
||||||
widget.novel.createDate.toString().substring(0, 10),
|
widget.novel.createDate.toString().substring(0, 10),
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey,
|
color: ColorScheme.of(context).outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -336,6 +340,9 @@ class _NovelPageState extends State<NovelPage> {
|
|||||||
Button(
|
Button(
|
||||||
onPressed: favorite,
|
onPressed: favorite,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: constrains.maxWidth > 420
|
||||||
|
? MainAxisAlignment.start
|
||||||
|
: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (isAddingFavorite)
|
if (isAddingFavorite)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -353,8 +360,9 @@ class _NovelPageState extends State<NovelPage> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
const Icon(MdIcons.favorite_outline, size: 18),
|
const Icon(MdIcons.favorite_outline, size: 18),
|
||||||
|
if (constrains.maxWidth > 420)
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text("Favorite".tl)
|
if (constrains.maxWidth > 420) Text("Favorite".tl)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.fixWidth(shouldFillSpace
|
.fixWidth(shouldFillSpace
|
||||||
@@ -365,10 +373,14 @@ class _NovelPageState extends State<NovelPage> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Button(
|
Button(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: constrains.maxWidth > 420
|
||||||
|
? MainAxisAlignment.start
|
||||||
|
: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(MdIcons.comment, size: 18),
|
const Icon(MdIcons.comment, size: 18),
|
||||||
|
if (constrains.maxWidth > 420)
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text("Comments".tl)
|
if (constrains.maxWidth > 420) Text("Comments".tl)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.fixWidth(shouldFillSpace
|
.fixWidth(shouldFillSpace
|
||||||
|
@@ -1,10 +1,17 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.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/md.dart';
|
||||||
|
import 'package:pixes/components/page_route.dart';
|
||||||
|
import 'package:pixes/components/title_bar.dart';
|
||||||
|
import 'package:pixes/foundation/app.dart';
|
||||||
import 'package:pixes/foundation/image_provider.dart';
|
import 'package:pixes/foundation/image_provider.dart';
|
||||||
import 'package:pixes/network/network.dart';
|
import 'package:pixes/network/network.dart';
|
||||||
import 'package:pixes/pages/image_page.dart';
|
import 'package:pixes/pages/image_page.dart';
|
||||||
|
import 'package:pixes/pages/main_page.dart';
|
||||||
import 'package:pixes/utils/ext.dart';
|
import 'package:pixes/utils/ext.dart';
|
||||||
|
import 'package:pixes/utils/translation.dart';
|
||||||
|
|
||||||
class NovelReadingPage extends StatefulWidget {
|
class NovelReadingPage extends StatefulWidget {
|
||||||
const NovelReadingPage(this.novel, {super.key});
|
const NovelReadingPage(this.novel, {super.key});
|
||||||
@@ -16,6 +23,37 @@ class NovelReadingPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
|
class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
|
||||||
|
TitleBarAction? action;
|
||||||
|
|
||||||
|
bool isShowingSettings = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
action = TitleBarAction(MdIcons.tune, "Settings", () {
|
||||||
|
if (!isShowingSettings) {
|
||||||
|
_NovelReadingSettings.show(context, () {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
isShowingSettings = true;
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
isShowingSettings = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
|
StateController.find<TitleBarController>().addAction(action!);
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
|
StateController.find<TitleBarController>().removeAction(action!);
|
||||||
|
});
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildContent(BuildContext context, String data) {
|
Widget buildContent(BuildContext context, String data) {
|
||||||
var content = buildList(context).toList();
|
var content = buildList(context).toList();
|
||||||
@@ -41,8 +79,12 @@ class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Widget> buildList(BuildContext context) sync* {
|
Iterable<Widget> buildList(BuildContext context) sync* {
|
||||||
|
double fontSizeAdd = appdata.settings["readingFontSize"] - 16.0;
|
||||||
|
double fontHeight = appdata.settings["readingLineHeight"];
|
||||||
|
|
||||||
yield Text(widget.novel.title,
|
yield Text(widget.novel.title,
|
||||||
style: const TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold));
|
style: TextStyle(
|
||||||
|
fontSize: 24.0 + fontSizeAdd, fontWeight: FontWeight.bold));
|
||||||
yield const SizedBox(height: 12.0);
|
yield const SizedBox(height: 12.0);
|
||||||
yield const Divider(
|
yield const Divider(
|
||||||
style: DividerThemeData(horizontalMargin: EdgeInsets.all(0)),
|
style: DividerThemeData(horizontalMargin: EdgeInsets.all(0)),
|
||||||
@@ -71,9 +113,150 @@ class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else if (content.startsWith('[chapter:')) {
|
||||||
|
var title = content.replaceLast(']', '').split(':')[1];
|
||||||
|
yield Text(title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.0 + fontSizeAdd,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
height: fontHeight))
|
||||||
|
.paddingBottom(8);
|
||||||
} else {
|
} else {
|
||||||
yield Text(content);
|
yield Text(content,
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 16.0 + fontSizeAdd, height: fontHeight))
|
||||||
|
.paddingBottom(appdata.settings["readingParagraphSpacing"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _NovelReadingSettings extends StatefulWidget {
|
||||||
|
const _NovelReadingSettings(this.callback);
|
||||||
|
|
||||||
|
final void Function() callback;
|
||||||
|
|
||||||
|
static void show(BuildContext context, void Function() callback) {
|
||||||
|
Navigator.of(context).push(SideBarRoute(_NovelReadingSettings(callback)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_NovelReadingSettings> createState() => __NovelReadingSettingsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class __NovelReadingSettingsState extends State<_NovelReadingSettings> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TitleBar(title: "Reading Settings".tl),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Card(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Font Size".tl),
|
||||||
|
subtitle: Slider(
|
||||||
|
value: appdata.settings["readingFontSize"],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
appdata.settings["readingFontSize"] = value;
|
||||||
|
});
|
||||||
|
appdata.writeSettings();
|
||||||
|
widget.callback();
|
||||||
|
},
|
||||||
|
min: 12.0,
|
||||||
|
max: 24.0,
|
||||||
|
divisions: 12,
|
||||||
|
label: appdata.settings["readingFontSize"].toString(),
|
||||||
|
),
|
||||||
|
trailing: Text(appdata.settings["readingFontSize"].toString()),
|
||||||
|
),
|
||||||
|
).paddingHorizontal(8).paddingBottom(8),
|
||||||
|
Card(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Line Height".tl),
|
||||||
|
subtitle: Slider(
|
||||||
|
value: appdata.settings["readingLineHeight"],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
appdata.settings["readingLineHeight"] = value;
|
||||||
|
});
|
||||||
|
appdata.writeSettings();
|
||||||
|
widget.callback();
|
||||||
|
},
|
||||||
|
min: 1.0,
|
||||||
|
max: 2.0,
|
||||||
|
divisions: 10,
|
||||||
|
label: appdata.settings["readingLineHeight"].toString(),
|
||||||
|
),
|
||||||
|
trailing: Text(appdata.settings["readingLineHeight"].toString()),
|
||||||
|
),
|
||||||
|
).paddingHorizontal(8).paddingBottom(8),
|
||||||
|
Card(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Paragraph Spacing".tl),
|
||||||
|
subtitle: Slider(
|
||||||
|
value: appdata.settings["readingParagraphSpacing"],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
appdata.settings["readingParagraphSpacing"] = value;
|
||||||
|
});
|
||||||
|
appdata.writeSettings();
|
||||||
|
widget.callback();
|
||||||
|
},
|
||||||
|
min: 0.0,
|
||||||
|
max: 16.0,
|
||||||
|
divisions: 8,
|
||||||
|
label: appdata.settings["readingParagraphSpacing"].toString(),
|
||||||
|
),
|
||||||
|
trailing:
|
||||||
|
Text(appdata.settings["readingParagraphSpacing"].toString()),
|
||||||
|
),
|
||||||
|
).paddingHorizontal(8).paddingBottom(8),
|
||||||
|
// 深色模式
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Theme".tl),
|
||||||
|
trailing: DropDownButton(
|
||||||
|
title: Text(appdata.settings["theme"] ?? "System".tl),
|
||||||
|
items: [
|
||||||
|
MenuFlyoutItem(
|
||||||
|
text: Text("System".tl),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
appdata.settings["theme"] = "System";
|
||||||
|
});
|
||||||
|
appdata.writeData();
|
||||||
|
StateController.findOrNull(tag: "MyApp")?.update();
|
||||||
|
}),
|
||||||
|
MenuFlyoutItem(
|
||||||
|
text: Text("light".tl),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
appdata.settings["theme"] = "Light";
|
||||||
|
});
|
||||||
|
appdata.writeData();
|
||||||
|
StateController.findOrNull(tag: "MyApp")?.update();
|
||||||
|
}),
|
||||||
|
MenuFlyoutItem(
|
||||||
|
text: Text("dark".tl),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
appdata.settings["theme"] = "Dark";
|
||||||
|
});
|
||||||
|
appdata.writeData();
|
||||||
|
StateController.findOrNull(tag: "MyApp")?.update();
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user