From 2826f9f59257ff68e4af041e994bebe359ba8e67 Mon Sep 17 00:00:00 2001 From: wgh136 Date: Wed, 15 May 2024 11:16:05 +0800 Subject: [PATCH] settings --- lib/appdata.dart | 34 +++-- lib/foundation/app.dart | 2 + lib/network/download.dart | 12 +- lib/network/network.dart | 7 ++ lib/pages/settings_page.dart | 234 ++++++++++++++++++++++++++++++++--- 5 files changed, 263 insertions(+), 26 deletions(-) diff --git a/lib/appdata.dart b/lib/appdata.dart index 90243da..c73618d 100644 --- a/lib/appdata.dart +++ b/lib/appdata.dart @@ -20,11 +20,28 @@ class _Appdata { "useTranslatedNameForDownload": false, }; + bool lock = false; + void writeData() async { + while (lock) { + await Future.delayed(const Duration(milliseconds: 20)); + } + lock = true; await File("${App.dataPath}/account.json") .writeAsString(jsonEncode(account)); await File("${App.dataPath}/settings.json") .writeAsString(jsonEncode(settings)); + lock = false; + } + + void writeSettings() async { + while (lock) { + await Future.delayed(const Duration(milliseconds: 20)); + } + lock = true; + await File("${App.dataPath}/settings.json") + .writeAsString(jsonEncode(settings)); + lock = false; } Future readData() async { @@ -35,17 +52,17 @@ class _Appdata { final settingsFile = File("${App.dataPath}/settings.json"); if (settingsFile.existsSync()) { var json = jsonDecode(await settingsFile.readAsString()); - for(var key in json.keys) { + for (var key in json.keys) { settings[key] = json[key]; } } - if(settings["downloadPath"] == null) { + if (settings["downloadPath"] == null) { settings["downloadPath"] = await _defaultDownloadPath; } } - Future get _defaultDownloadPath async{ - if(App.isAndroid) { + Future get _defaultDownloadPath async { + if (App.isAndroid) { String? downloadPath = "/storage/emulated/0/download"; if (!Directory(downloadPath).havePermission()) { downloadPath = null; @@ -53,14 +70,15 @@ class _Appdata { var res = downloadPath; res ??= (await getExternalStorageDirectory())!.path; return "$res/pixes"; - } else if (App.isWindows){ - var res = await const MethodChannel("pixes/picture_folder").invokeMethod(""); - if(res != "error") { + } else if (App.isWindows) { + var res = + await const MethodChannel("pixes/picture_folder").invokeMethod(""); + if (res != "error") { return res + "/pixes"; } } else if (App.isMacOS || App.isLinux) { var downloadPath = (await getDownloadsDirectory())?.path; - if(downloadPath != null && Directory(downloadPath).havePermission()) { + if (downloadPath != null && Directory(downloadPath).havePermission()) { return "$downloadPath/pixes"; } } diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index a03bce4..91df7f4 100644 --- a/lib/foundation/app.dart +++ b/lib/foundation/app.dart @@ -9,6 +9,8 @@ export "state_controller.dart"; export "navigation.dart"; class _App { + final version = "1.0.0"; + bool get isAndroid => Platform.isAndroid; bool get isIOS => Platform.isIOS; bool get isWindows => Platform.isWindows; diff --git a/lib/network/download.dart b/lib/network/download.dart index c75be41..c00f8af 100644 --- a/lib/network/download.dart +++ b/lib/network/download.dart @@ -114,11 +114,15 @@ class DownloadingTask { static String _generateFilePath(Illust illust, int index, String ext) { final String downloadPath = appdata.settings["downloadPath"]; String subPathPatten = appdata.settings["downloadSubPath"]; - final tags = appdata.settings["useTranslatedNameForDownload"] == false - ? illust.tags.map((e) => e.name).toList() - : illust.tags.map((e) => e.translatedName ?? e.name).toList(); final tagsWeight = (appdata.settings["tagsWeight"] as String).split(' '); - tags.sort((a, b) => tagsWeight.indexOf(a) - tagsWeight.indexOf(b)); + final originalTags = List.from(illust.tags); + originalTags.sort((a, b){ + return tagsWeight.indexOf(a.name) - tagsWeight.indexOf(b.name); + }); + final tags = appdata.settings["useTranslatedNameForDownload"] == false + ? originalTags.map((e) => e.name).toList() + : originalTags.map((e) => e.translatedName ?? e.name).toList(); + subPathPatten = subPathPatten.replaceAll(r"${id}", illust.id.toString()); subPathPatten = subPathPatten.replaceAll(r"${title}", illust.title); subPathPatten = subPathPatten.replaceAll(r"${author}", illust.author.name); diff --git a/lib/network/network.dart b/lib/network/network.dart index a12596d..3407a26 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -106,7 +106,14 @@ class Network { }, options: Options( contentType: Headers.formUrlEncodedContentType, + validateStatus: (i) => true, headers: headers)); + if(res.statusCode != 200) { + var data = res.data ?? ""; + if(data.contains("Invalid refresh token")) { + throw "Failed to refresh token. Plaese logout and re-login"; + } + } var account = Account.fromJson(json.decode(res.data!)); appdata.account = account; appdata.writeData(); diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 81d578e..d8d6d4f 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -1,9 +1,14 @@ +import 'dart:io'; + import 'package:fluent_ui/fluent_ui.dart'; import 'package:pixes/appdata.dart'; +import 'package:pixes/components/md.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/pages/main_page.dart'; +import 'package:pixes/utils/io.dart'; import 'package:pixes/utils/translation.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -24,7 +29,12 @@ class _SettingsPageState extends State { SliverTitleBar(title: "Settings".tl), buildHeader("Account".tl), buildAccount(), - SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom)), + buildHeader("Download".tl), + buildDownload(), + buildHeader("About".tl), + buildAbout(), + SliverPadding( + padding: EdgeInsets.only(bottom: context.padding.bottom)), ], ), ); @@ -34,8 +44,10 @@ class _SettingsPageState extends State { return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: Text(text, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - ),); + child: Text(text, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + ), + ); } Widget buildItem({required String title, String? subtitle, Widget? action}) { @@ -49,8 +61,8 @@ class _SettingsPageState extends State { ), ); } - - Widget buildAccount(){ + + Widget buildAccount() { return SliverToBoxAdapter( child: Column( children: [ @@ -71,8 +83,7 @@ class _SettingsPageState extends State { App.rootNavigatorKey.currentState!.pushAndRemoveUntil( AppPageRoute( builder: (context) => const MainPage()), - (route) => false - ); + (route) => false); }, ), FilledButton( @@ -86,14 +97,209 @@ class _SettingsPageState extends State { child: Text("Continue".tl).fixWidth(64), ), ), - buildItem(title: "Account Settings".tl, action: Button( - child: Text("Edit".tl).fixWidth(64), - onPressed: (){ - launchUrlString("https://www.pixiv.net/setting_user.php"); - }, - )), + buildItem( + title: "Account Settings".tl, + action: Button( + child: Text("Edit".tl).fixWidth(64), + onPressed: () { + launchUrlString("https://www.pixiv.net/setting_user.php"); + }, + )), ], ), ); } -} \ No newline at end of file + + Widget buildDownload() { + return SliverToBoxAdapter( + child: Column( + children: [ + buildItem( + title: "Download Path", + subtitle: appdata.settings["downloadPath"], + action: Button( + child: Text("Manage".tl).fixWidth(64), + onPressed: () { + if (Platform.isIOS) { + showToast(context, message: "Unsupport platform".tl); + return; + } + context.to(() => const _SetDownloadPathPage()); + }), + ), + buildItem( + title: "Subpath", + subtitle: appdata.settings["downloadSubPath"], + action: Button( + child: Text("Manage".tl).fixWidth(64), + onPressed: () { + context.to(() => const _SetDownloadSubPathPage()); + }), + ), + ], + ), + ); + } + + Widget buildAbout() { + return SliverToBoxAdapter( + child: Column( + children: [ + buildItem(title: "Version", subtitle: App.version), + buildItem( + title: "Github", + action: IconButton( + icon: const Icon(MdIcons.open_in_new, size: 18,), + onPressed: () => + launchUrlString("https://github.com/wgh136/pixes"), + )), + buildItem( + title: "Telegram", + action: IconButton( + icon: const Icon(MdIcons.open_in_new, size: 18,), + onPressed: () => + launchUrlString("https://t.me/pica_group"), + )), + ], + ), + ); + } +} + +class _SetDownloadPathPage extends StatefulWidget { + const _SetDownloadPathPage(); + + @override + State<_SetDownloadPathPage> createState() => __SetDownloadPathPageState(); +} + +class __SetDownloadPathPageState extends State<_SetDownloadPathPage> { + final controller = + TextEditingController(text: appdata.settings["downloadPath"]); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleBar(title: "Download Path".tl), + TextBox( + controller: controller, + ).paddingHorizontal(16), + const SizedBox( + height: 8, + ), + Button( + child: Text("Confirm".tl), + onPressed: () { + var text = controller.text; + if (Directory(text).havePermission()) { + appdata.settings["downloadPath"] = text; + appdata.writeData(); + context.pop(); + } else { + showToast(context, message: "No Permission".tl); + } + }, + ).toAlign(Alignment.centerRight).paddingRight(16), + ], + ); + } +} + +class _SetDownloadSubPathPage extends StatefulWidget { + const _SetDownloadSubPathPage({super.key}); + + @override + State<_SetDownloadSubPathPage> createState() => + __SetDownloadSubPathPageState(); +} + +class __SetDownloadSubPathPageState extends State<_SetDownloadSubPathPage> { + final controller = + TextEditingController(text: appdata.settings["downloadSubPath"]); + final controller2 = + TextEditingController(text: appdata.settings["tagsWeight"]); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleBar(title: "Download Subpath".tl), + Text("Rule".tl) + .padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 16)), + TextBox( + controller: controller, + ).paddingHorizontal(16), + Text("Weights of the tags".tl) + .padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 16)), + TextBox( + controller: controller2, + ).paddingHorizontal(16), + const SizedBox( + height: 8, + ), + ListTile( + title: Text("Use translated tag name".tl), + trailing: ToggleSwitch( + checked: appdata.settings["useTranslatedNameForDownload"], + onChanged: (value) { + appdata.settings["useTranslatedNameForDownload"] = value; + appdata.writeSettings(); + }, + ), + ), + const SizedBox( + height: 8, + ), + Button( + child: Text("Confirm".tl), + onPressed: () { + var text = controller.text; + if (check(text)) { + appdata.settings["downloadSubPath"] = text; + appdata.writeData(); + context.pop(); + } else { + showToast(context, message: "No Permission".tl); + } + }, + ).toAlign(Alignment.centerRight).paddingRight(16), + const SizedBox( + height: 16, + ), + Text(_instruction).paddingHorizontal(16) + ], + ), + ); + } + + bool check(String text) { + if (!text.startsWith('/') || !text.startsWith('\\')) { + return false; + } + return true; + } + + String get _instruction => """ +${"Edit the rule of where to store a image.".tl} +${"Note: The rule should contain file name.".tl} + +${"Some keyword will be replaced as following rule:"} + \${title} -> ${"Title of the actwork".tl} + \${author} -> ${"Name of the author".tl} + \${id} -> ${"Actwork ID".tl} + \${index} -> ${"Index of the image in the artwork".tl} + \${ext} -> ${"File extension".tl} + ${"Tags: Tags will be sorted with \"Weights of tags\" setting and be replaced with following rule".tl} + ${"The final text will be affect by the setting og \"Use translated tag name\"".tl} + \${tag0} -> ${"The first tag of the artwork".tl} + \${tag1} -> ${"The sencondary tag of the artwork".tl} + +${"Weights of the tags".tl}: +Filled with tags. The tags should be splited with a space. The tag in the front have higher weight. +It is required to use originlal name instead of translated name. +"""; +}