From f33df47cd6d6660ae2b9482f4c5bd85a7568ab2e Mon Sep 17 00:00:00 2001 From: wgh19 Date: Mon, 20 May 2024 21:58:58 +0800 Subject: [PATCH] novel reading settings; improve ui --- assets/tr.json | 16 ++- lib/appdata.dart | 3 + lib/pages/illust_page.dart | 1 - lib/pages/main_page.dart | 97 +++++++++++----- lib/pages/novel_page.dart | 26 +++-- lib/pages/novel_reading_page.dart | 187 +++++++++++++++++++++++++++++- 6 files changed, 292 insertions(+), 38 deletions(-) diff --git a/assets/tr.json b/assets/tr.json index c030fd1..170897e 100644 --- a/assets/tr.json +++ b/assets/tr.json @@ -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": "深色" } } \ No newline at end of file diff --git a/lib/appdata.dart b/lib/appdata.dart index 59f00ac..86db39d 100644 --- a/lib/appdata.dart +++ b/lib/appdata.dart @@ -20,6 +20,9 @@ class _Appdata { "proxy": "", "darkMode": "System", "language": "System", + "readingFontSize": 16.0, + "readingLineHeight": 1.5, + "readingParagraphSpacing": 8.0, }; bool lock = false; diff --git a/lib/pages/illust_page.dart b/lib/pages/illust_page.dart index 22f1549..b156765 100644 --- a/lib/pages/illust_page.dart +++ b/lib/pages/illust_page.dart @@ -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'; diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 7e1530a..6f97af0 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -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 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 with WindowListener { @override void initState() { + StateController.put(TitleBarController()); windowManager.addListener(this); listenMouseSideButtonToBack(navigatorKey); App.mainNavigatorKey = navigatorKey; @@ -54,6 +81,7 @@ class _MainPageState extends State with WindowListener { @override void dispose() { + StateController.remove(); windowManager.removeListener(this); super.dispose(); } @@ -224,33 +252,50 @@ class _MainPageState extends State 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), - child: Align( - alignment: AlignmentDirectional.centerStart, - child: Row( - children: [ - Text( - "Pixes", - style: TextStyle(fontSize: 13), - ), - Spacer(), - if (kDebugMode) - Padding( - padding: EdgeInsets.only(right: 138), - child: Button(onPressed: debug, child: Text("Debug")), - ) - ], + return StateBuilder( + builder: (controller) { + Widget content = Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + if (!App.isDesktop) + const Text( + "Pixes", + style: TextStyle(fontSize: 13), + ), + 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), diff --git a/lib/pages/novel_page.dart b/lib/pages/novel_page.dart index 21a986b..bd2219c 100644 --- a/lib/pages/novel_page.dart +++ b/lib/pages/novel_page.dart @@ -53,7 +53,10 @@ class _NovelPageState extends State { ), 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 { 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 { )), 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 { 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 { ) else const Icon(MdIcons.favorite_outline, size: 18), - const SizedBox(width: 12), - Text("Favorite".tl) + if (constrains.maxWidth > 420) + const SizedBox(width: 12), + if (constrains.maxWidth > 420) Text("Favorite".tl) ], ) .fixWidth(shouldFillSpace @@ -365,10 +373,14 @@ class _NovelPageState extends State { const SizedBox(width: 8), Button( child: Row( + mainAxisAlignment: constrains.maxWidth > 420 + ? MainAxisAlignment.start + : MainAxisAlignment.center, children: [ const Icon(MdIcons.comment, size: 18), - const SizedBox(width: 12), - Text("Comments".tl) + if (constrains.maxWidth > 420) + const SizedBox(width: 12), + if (constrains.maxWidth > 420) Text("Comments".tl) ], ) .fixWidth(shouldFillSpace diff --git a/lib/pages/novel_reading_page.dart b/lib/pages/novel_reading_page.dart index dae0dac..302a86f 100644 --- a/lib/pages/novel_reading_page.dart +++ b/lib/pages/novel_reading_page.dart @@ -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 { + 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().addAction(action!); + }); + super.initState(); + } + + @override + void dispose() { + Future.delayed(const Duration(milliseconds: 200), () { + StateController.find().removeAction(action!); + }); + super.dispose(); + } + @override Widget buildContent(BuildContext context, String data) { var content = buildList(context).toList(); @@ -41,8 +79,12 @@ class _NovelReadingPageState extends LoadingState { } Iterable 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 { ), ), ); + } 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(); + }), + ]), + ), + ), + ], + ), + ); + } +}