diff --git a/lib/components/labeled_checkbox.dart b/lib/components/labeled_checkbox.dart new file mode 100644 index 0000000..54f41b6 --- /dev/null +++ b/lib/components/labeled_checkbox.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class LabeledCheckbox extends StatelessWidget { + const LabeledCheckbox({ + Key? key, + required this.label, + required this.value, + required this.onChanged, + }) : super(key: key); + + final Text label; + final bool value; + final ValueChanged? onChanged; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => onChanged?.call(!value), + child: Row( + children: [ + Checkbox( + value: value, + onChanged: onChanged, + ), + Expanded(child: label), + ], + ), + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1b09cb2..6f15be5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -107,5 +107,18 @@ "enterNewCookies": "Enter new cookies here", "maxTaskCount": "Maximum number of parallel tasks", "invalidNumber": "Invalid number.", - "numberOutOfRange": "The number is out of range." + "numberOutOfRange": "The number is out of range.", + "maxRetryCount": "Maximum retry count", + "maxDownloadImgCount": "Maximum number of parallel downloads of images", + "listeningPort": "Listening port", + "listeningHostname": "Listening host", + "meiliHost": "Meilisearch server host", + "meiliUpdateApiKey": "Meilisearch API key for updating gallery metadata", + "meiliSearchApiKey": "Meilisearch API key for searching", + "ffmpegPath": "The path to the ffmpeg binary", + "thumbnailMethod": "The method used to generate thumbnail", + "thumbnailMethod0": "ffmpeg binary", + "thumbnailMethod1": "ffmpeg API", + "thumbnailDir": "The folder used to store thumbnails", + "imgVerifySecret": "The secret of image verify" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 2d244de..7621701 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -107,5 +107,18 @@ "enterNewCookies": "在此输入新的 Cookies", "maxTaskCount": "最大并行任务数", "invalidNumber": "非法的数字。", - "numberOutOfRange": "数字超出了范围。" + "numberOutOfRange": "数字超出了范围。", + "maxRetryCount": "最大重试次数", + "maxDownloadImgCount": "最大图片并行下载数", + "listeningPort": "监听端口", + "listeningHostname": "监听主机", + "meiliHost": "Meilisearch 服务器主机", + "meiliUpdateApiKey": "用于更新画廊元数据的 Meilisearch API 密钥", + "meiliSearchApiKey": "用于搜索的 Meilisearch API 密钥", + "ffmpegPath": "FFMPEG 二进制的位置", + "thumbnailMethod": "生成缩略图的方式", + "thumbnailMethod0": "FFMPEG 二进制", + "thumbnailMethod1": "FFMPEG API", + "thumbnailDir": "存放缩略图的文件夹", + "imgVerifySecret": "用于验证图片 verify 的密钥" } diff --git a/lib/server_settings.dart b/lib/server_settings.dart index eae7c42..9f073ff 100644 --- a/lib/server_settings.dart +++ b/lib/server_settings.dart @@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'api/config.dart'; +import 'components/labeled_checkbox.dart'; import 'components/number_field.dart'; import 'globals.dart'; import 'platform/ua.dart'; @@ -202,7 +203,7 @@ class _ServerSettingsPage extends State return _buildWithHorizontalPadding( context, Column(mainAxisSize: MainAxisSize.min, children: [ - _buildWithVecticalPadding(CheckboxMenuButton( + _buildWithVecticalPadding(LabeledCheckbox( value: _now.ex ?? _config!.ex, onChanged: (b) { if (b != null) { @@ -212,8 +213,8 @@ class _ServerSettingsPage extends State }); } }, - child: Text(i18n.useEx))), - _buildWithVecticalPadding(CheckboxMenuButton( + label: Text(i18n.useEx))), + _buildWithVecticalPadding(LabeledCheckbox( value: _now.mpv ?? _config!.mpv, onChanged: (b) { if (b != null) { @@ -223,8 +224,8 @@ class _ServerSettingsPage extends State }); } }, - child: Text(i18n.mpv))), - _buildWithVecticalPadding(CheckboxMenuButton( + label: Text(i18n.mpv))), + _buildWithVecticalPadding(LabeledCheckbox( value: _now.downloadOriginalImg ?? _config!.downloadOriginalImg, onChanged: (b) { if (b != null) { @@ -234,8 +235,8 @@ class _ServerSettingsPage extends State }); } }, - child: Text(i18n.downloadOriginalImg))), - _buildWithVecticalPadding(CheckboxMenuButton( + label: Text(i18n.downloadOriginalImg))), + _buildWithVecticalPadding(LabeledCheckbox( value: _now.exportZipJpnTitle ?? _config!.exportZipJpnTitle, onChanged: (b) { if (b != null) { @@ -245,8 +246,8 @@ class _ServerSettingsPage extends State }); } }, - child: Text(i18n.exportZipJpnTitle))), - _buildWithVecticalPadding(CheckboxMenuButton( + label: Text(i18n.exportZipJpnTitle))), + _buildWithVecticalPadding(LabeledCheckbox( value: _now.removePreviousGallery ?? _config!.removePreviousGallery, onChanged: (b) { @@ -257,7 +258,7 @@ class _ServerSettingsPage extends State }); } }, - child: Text(i18n.removePreviousGallery))), + label: Text(i18n.removePreviousGallery))), ])); } @@ -347,6 +348,169 @@ class _ServerSettingsPage extends State } }, )), + _buildWithVecticalPadding(NumberFormField( + min: 1, + initialValue: _now.maxRetryCount ?? _config!.maxRetryCount, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.maxRetryCount, + ), + onChanged: (s) { + if (s != null) { + setState(() { + _now.maxRetryCount = s; + _changed = true; + }); + } + }, + )), + _buildWithVecticalPadding(NumberFormField( + min: 1, + initialValue: + _now.maxDownloadImgCount ?? _config!.maxDownloadImgCount, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.maxDownloadImgCount, + ), + onChanged: (s) { + if (s != null) { + setState(() { + _now.maxDownloadImgCount = s; + _changed = true; + }); + } + }, + )), + _buildWithVecticalPadding(NumberFormField( + min: 0, + max: 65535, + initialValue: _now.port ?? _config!.port, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.listeningPort, + ), + onChanged: (s) { + if (s != null) { + setState(() { + _now.port = s; + _changed = true; + }); + } + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.hostname ?? _config!.hostname, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.listeningHostname, + ), + onChanged: (s) { + setState(() { + _now.hostname = s; + _changed = true; + }); + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.meiliHost ?? _config!.meiliHost, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.meiliHost, + ), + onChanged: (s) { + setState(() { + _now.meiliHost = s; + _changed = true; + }); + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.meiliSearchApiKey ?? _config!.meiliSearchApiKey, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.meiliSearchApiKey, + ), + onChanged: (s) { + setState(() { + _now.meiliSearchApiKey = s; + _changed = true; + }); + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.meiliUpdateApiKey ?? _config!.meiliUpdateApiKey, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.meiliUpdateApiKey, + ), + onChanged: (s) { + setState(() { + _now.meiliUpdateApiKey = s; + _changed = true; + }); + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.ffmpegPath ?? _config!.ffmpegPath, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.ffmpegPath, + ), + onChanged: (s) { + setState(() { + _now.ffmpegPath = s; + _changed = true; + }); + }, + )), + _buildWithVecticalPadding(DropdownButtonFormField( + items: [ + DropdownMenuItem( + value: ThumbnailMethod.ffmpegBinary, + child: Text(i18n.thumbnailMethod0)), + DropdownMenuItem( + value: ThumbnailMethod.ffmpegApi, + child: Text(i18n.thumbnailMethod1)), + ], + onChanged: (v) { + if (v != null) { + setState(() { + _now.thumbnailMethod = v; + _changed = true; + }); + } + }, + value: _now.thumbnailMethod ?? _config!.thumbnailMethod, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.thumbnailMethod, + ))), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.thumbnailDir ?? _config!.thumbnailDir, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.thumbnailDir, + ), + onChanged: (s) { + setState(() { + _now.thumbnailDir = s; + _changed = true; + }); + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _now.imgVerifySecret ?? _config!.imgVerifySecret, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.imgVerifySecret, + ), + onChanged: (s) { + setState(() { + _now.imgVerifySecret = s; + _changed = true; + }); + }, + )), ])); }