novel reading settings; improve ui

This commit is contained in:
wgh19
2024-05-20 21:58:58 +08:00
parent c51df1efde
commit f33df47cd6
6 changed files with 292 additions and 38 deletions

View File

@@ -132,7 +132,13 @@
"Replace with '-p${index}' if the work have more than one images, otherwise replace with blank.": "替换为'-p${index}'如果作品有多张图片, 否则替换为空白",
"Recommendation": "推荐",
"Novel": "小说",
"Novels": "小说"
"Novels": "小说",
"Reading Settings": "阅读设置",
"Font Size": "字体大小",
"Line Height": "行高",
"Paragraph Spacing": "段间距",
"light": "浅色",
"dark": "深色"
},
"zh_TW": {
"Search": "搜索",
@@ -267,6 +273,12 @@
"Replace with '-p${index}' if the work have more than one images, otherwise replace with blank.": "替換為'-p${index}'如果作品有多張圖片, 否則替換為空白",
"Recommendation": "推薦",
"Novel": "小說",
"Novels": "小說"
"Novels": "小說",
"Reading Settings": "閱讀設置",
"Font Size": "字體大小",
"Line Height": "行高",
"Paragraph Spacing": "段間距",
"light": "淺色",
"dark": "深色"
}
}

View File

@@ -20,6 +20,9 @@ class _Appdata {
"proxy": "",
"darkMode": "System",
"language": "System",
"readingFontSize": 16.0,
"readingLineHeight": 1.5,
"readingParagraphSpacing": 8.0,
};
bool lock = false;

View File

@@ -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/loading.dart';
import 'package:pixes/components/message.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';

View File

@@ -30,6 +30,32 @@ import "downloading_page.dart";
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 {
const MainPage({super.key});
@@ -46,6 +72,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
@override
void initState() {
StateController.put<TitleBarController>(TitleBarController());
windowManager.addListener(this);
listenMouseSideButtonToBack(navigatorKey);
App.mainNavigatorKey = navigatorKey;
@@ -54,6 +81,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
@override
void dispose() {
StateController.remove<TitleBarController>();
windowManager.removeListener(this);
super.dispose();
}
@@ -224,33 +252,50 @@ class _MainPageState extends State<MainPage> with WindowListener {
automaticallyImplyLeading: false,
height: _appBarHeight,
title: () {
if (!App.isDesktop) {
return const Align(
alignment: AlignmentDirectional.centerStart,
child: Text("pixes"),
);
}
return const DragToMoveArea(
child: Padding(
padding: EdgeInsets.only(bottom: 4),
return StateBuilder<TitleBarController>(
builder: (controller) {
Widget content = Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Align(
alignment: AlignmentDirectional.centerStart,
child: Row(
children: [
Text(
if (!App.isDesktop)
const Text(
"Pixes",
style: TextStyle(fontSize: 13),
),
Spacer(),
if (kDebugMode)
Padding(
padding: EdgeInsets.only(right: 138),
child: Button(onPressed: debug, child: Text("Debug")),
)
if (!App.isDesktop) const Spacer(),
if (App.isDesktop)
const Expanded(
child: DragToMoveArea(
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),

View File

@@ -53,7 +53,10 @@ class _NovelPageState extends State<NovelPage> {
),
if (widget.novel.seriesId != null)
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)));
@@ -240,6 +243,7 @@ class _NovelPageState extends State<NovelPage> {
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.novel.author.name,
style: const TextStyle(
@@ -248,9 +252,9 @@ class _NovelPageState extends State<NovelPage> {
)),
Text(
widget.novel.createDate.toString().substring(0, 10),
style: const TextStyle(
style: TextStyle(
fontSize: 12,
color: Colors.grey,
color: ColorScheme.of(context).outline,
),
),
],
@@ -336,6 +340,9 @@ class _NovelPageState extends State<NovelPage> {
Button(
onPressed: favorite,
child: Row(
mainAxisAlignment: constrains.maxWidth > 420
? MainAxisAlignment.start
: MainAxisAlignment.center,
children: [
if (isAddingFavorite)
const SizedBox(
@@ -353,8 +360,9 @@ class _NovelPageState extends State<NovelPage> {
)
else
const Icon(MdIcons.favorite_outline, size: 18),
if (constrains.maxWidth > 420)
const SizedBox(width: 12),
Text("Favorite".tl)
if (constrains.maxWidth > 420) Text("Favorite".tl)
],
)
.fixWidth(shouldFillSpace
@@ -365,10 +373,14 @@ class _NovelPageState extends State<NovelPage> {
const SizedBox(width: 8),
Button(
child: Row(
mainAxisAlignment: constrains.maxWidth > 420
? MainAxisAlignment.start
: MainAxisAlignment.center,
children: [
const Icon(MdIcons.comment, size: 18),
if (constrains.maxWidth > 420)
const SizedBox(width: 12),
Text("Comments".tl)
if (constrains.maxWidth > 420) Text("Comments".tl)
],
)
.fixWidth(shouldFillSpace

View File

@@ -1,10 +1,17 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/components/animated_image.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/network/network.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/translation.dart';
class NovelReadingPage extends StatefulWidget {
const NovelReadingPage(this.novel, {super.key});
@@ -16,6 +23,37 @@ class NovelReadingPage extends StatefulWidget {
}
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
Widget buildContent(BuildContext context, String data) {
var content = buildList(context).toList();
@@ -41,8 +79,12 @@ class _NovelReadingPageState extends LoadingState<NovelReadingPage, String> {
}
Iterable<Widget> buildList(BuildContext context) sync* {
double fontSizeAdd = appdata.settings["readingFontSize"] - 16.0;
double fontHeight = appdata.settings["readingLineHeight"];
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 Divider(
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 {
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();
}),
]),
),
),
],
),
);
}
}