mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-07-08 01:30:28 +08:00
Add server settings page
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:eh_downloader_flutter/api/file.dart';
|
|||||||
import 'package:retrofit/retrofit.dart';
|
import 'package:retrofit/retrofit.dart';
|
||||||
|
|
||||||
import 'api_result.dart';
|
import 'api_result.dart';
|
||||||
|
import 'config.dart';
|
||||||
import 'gallery.dart';
|
import 'gallery.dart';
|
||||||
import 'status.dart';
|
import 'status.dart';
|
||||||
import 'tags.dart';
|
import 'tags.dart';
|
||||||
@@ -191,6 +192,14 @@ abstract class _EHApi {
|
|||||||
{@Part(name: "is_nsfw") bool? isNsfw,
|
{@Part(name: "is_nsfw") bool? isNsfw,
|
||||||
@Part(name: "is_ad") bool? isAd,
|
@Part(name: "is_ad") bool? isAd,
|
||||||
@CancelRequest() CancelToken? cancel});
|
@CancelRequest() CancelToken? cancel});
|
||||||
|
|
||||||
|
@GET('/config')
|
||||||
|
Future<Config> getConfig(
|
||||||
|
{@Query("current") bool? current, @CancelRequest() CancelToken? cancel});
|
||||||
|
@POST('/config')
|
||||||
|
Future<UpdateConfigResult> updateConfig(
|
||||||
|
@Body(nullToAbsent: false) ConfigOptional cfg,
|
||||||
|
{@CancelRequest() CancelToken? cancel});
|
||||||
}
|
}
|
||||||
|
|
||||||
class EHApi extends __EHApi {
|
class EHApi extends __EHApi {
|
||||||
|
|||||||
@@ -857,6 +857,71 @@ class __EHApi implements _EHApi {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Config> getConfig({
|
||||||
|
bool? current,
|
||||||
|
CancelToken? cancel,
|
||||||
|
}) async {
|
||||||
|
const _extra = <String, dynamic>{};
|
||||||
|
final queryParameters = <String, dynamic>{r'current': current};
|
||||||
|
queryParameters.removeWhere((k, v) => v == null);
|
||||||
|
final _headers = <String, dynamic>{};
|
||||||
|
final Map<String, dynamic>? _data = null;
|
||||||
|
final _result =
|
||||||
|
await _dio.fetch<Map<String, dynamic>>(_setStreamType<Config>(Options(
|
||||||
|
method: 'GET',
|
||||||
|
headers: _headers,
|
||||||
|
extra: _extra,
|
||||||
|
)
|
||||||
|
.compose(
|
||||||
|
_dio.options,
|
||||||
|
'/config',
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: _data,
|
||||||
|
cancelToken: cancel,
|
||||||
|
)
|
||||||
|
.copyWith(
|
||||||
|
baseUrl: _combineBaseUrls(
|
||||||
|
_dio.options.baseUrl,
|
||||||
|
baseUrl,
|
||||||
|
))));
|
||||||
|
final value = Config.fromJson(_result.data!);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<UpdateConfigResult> updateConfig(
|
||||||
|
ConfigOptional cfg, {
|
||||||
|
CancelToken? cancel,
|
||||||
|
}) async {
|
||||||
|
const _extra = <String, dynamic>{};
|
||||||
|
final queryParameters = <String, dynamic>{};
|
||||||
|
queryParameters.removeWhere((k, v) => v == null);
|
||||||
|
final _headers = <String, dynamic>{};
|
||||||
|
final _data = <String, dynamic>{};
|
||||||
|
_data.addAll(cfg.toJson());
|
||||||
|
final _result = await _dio
|
||||||
|
.fetch<Map<String, dynamic>>(_setStreamType<UpdateConfigResult>(Options(
|
||||||
|
method: 'POST',
|
||||||
|
headers: _headers,
|
||||||
|
extra: _extra,
|
||||||
|
)
|
||||||
|
.compose(
|
||||||
|
_dio.options,
|
||||||
|
'/config',
|
||||||
|
queryParameters: queryParameters,
|
||||||
|
data: _data,
|
||||||
|
cancelToken: cancel,
|
||||||
|
)
|
||||||
|
.copyWith(
|
||||||
|
baseUrl: _combineBaseUrls(
|
||||||
|
_dio.options.baseUrl,
|
||||||
|
baseUrl,
|
||||||
|
))));
|
||||||
|
final value = UpdateConfigResult.fromJson(_result.data!);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||||
if (T != dynamic &&
|
if (T != dynamic &&
|
||||||
!(requestOptions.responseType == ResponseType.bytes ||
|
!(requestOptions.responseType == ResponseType.bytes ||
|
||||||
|
|||||||
167
lib/api/config.dart
Normal file
167
lib/api/config.dart
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'config.g.dart';
|
||||||
|
|
||||||
|
enum ThumbnailMethod {
|
||||||
|
@JsonValue(0)
|
||||||
|
ffmpegBinary,
|
||||||
|
@JsonValue(1)
|
||||||
|
ffmpegApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Config {
|
||||||
|
Config({
|
||||||
|
required this.cookies,
|
||||||
|
this.dbPath,
|
||||||
|
this.ua,
|
||||||
|
required this.ex,
|
||||||
|
required this.base,
|
||||||
|
required this.maxTaskCount,
|
||||||
|
required this.mpv,
|
||||||
|
required this.maxRetryCount,
|
||||||
|
required this.maxDownloadImgCount,
|
||||||
|
required this.downloadOriginalImg,
|
||||||
|
required this.port,
|
||||||
|
required this.exportZipJpnTitle,
|
||||||
|
required this.hostname,
|
||||||
|
this.meiliHost,
|
||||||
|
this.meiliSearchApiKey,
|
||||||
|
this.meiliUpdateApiKey,
|
||||||
|
required this.ffmpegPath,
|
||||||
|
required this.thumbnailMethod,
|
||||||
|
required this.thumbnailDir,
|
||||||
|
required this.removePreviousGallery,
|
||||||
|
this.imgVerifySecret,
|
||||||
|
this.meiliHosts,
|
||||||
|
required this.corsCredentialsHosts,
|
||||||
|
this.flutterFrontend,
|
||||||
|
required this.fetchTimeout,
|
||||||
|
required this.downloadTimeout,
|
||||||
|
required this.ffprobePath,
|
||||||
|
});
|
||||||
|
bool cookies;
|
||||||
|
@JsonKey(name: 'db_path')
|
||||||
|
String? dbPath;
|
||||||
|
String? ua;
|
||||||
|
bool ex;
|
||||||
|
String base;
|
||||||
|
@JsonKey(name: 'max_task_count')
|
||||||
|
int maxTaskCount;
|
||||||
|
bool mpv;
|
||||||
|
@JsonKey(name: 'max_retry_count')
|
||||||
|
int maxRetryCount;
|
||||||
|
@JsonKey(name: 'max_download_img_count')
|
||||||
|
int maxDownloadImgCount;
|
||||||
|
@JsonKey(name: 'download_original_img')
|
||||||
|
bool downloadOriginalImg;
|
||||||
|
int port;
|
||||||
|
@JsonKey(name: 'export_zip_jpn_title')
|
||||||
|
bool exportZipJpnTitle;
|
||||||
|
String hostname;
|
||||||
|
@JsonKey(name: 'meili_host')
|
||||||
|
String? meiliHost;
|
||||||
|
@JsonKey(name: 'meili_search_api_key')
|
||||||
|
String? meiliSearchApiKey;
|
||||||
|
@JsonKey(name: 'meili_update_api_key')
|
||||||
|
String? meiliUpdateApiKey;
|
||||||
|
@JsonKey(name: 'ffmpeg_path')
|
||||||
|
String ffmpegPath;
|
||||||
|
@JsonKey(name: 'thumbnail_method')
|
||||||
|
ThumbnailMethod thumbnailMethod;
|
||||||
|
@JsonKey(name: 'thumbnail_dir')
|
||||||
|
String thumbnailDir;
|
||||||
|
@JsonKey(name: 'remove_previous_gallery')
|
||||||
|
bool removePreviousGallery;
|
||||||
|
@JsonKey(name: 'img_verify_secret')
|
||||||
|
String? imgVerifySecret;
|
||||||
|
@JsonKey(name: 'meili_hosts')
|
||||||
|
Map<String, String>? meiliHosts;
|
||||||
|
@JsonKey(name: 'cors_credentials_hosts')
|
||||||
|
List<String> corsCredentialsHosts;
|
||||||
|
@JsonKey(name: 'flutter_frontend')
|
||||||
|
String? flutterFrontend;
|
||||||
|
@JsonKey(name: 'fetch_timeout')
|
||||||
|
int fetchTimeout;
|
||||||
|
@JsonKey(name: 'download_timeout')
|
||||||
|
int downloadTimeout;
|
||||||
|
@JsonKey(name: 'ffprobe_path')
|
||||||
|
String ffprobePath;
|
||||||
|
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$ConfigToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class UpdateConfigResult {
|
||||||
|
const UpdateConfigResult({
|
||||||
|
required this.isUnsafe,
|
||||||
|
});
|
||||||
|
@JsonKey(name: 'is_unsafe')
|
||||||
|
final bool isUnsafe;
|
||||||
|
factory UpdateConfigResult.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UpdateConfigResultFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$UpdateConfigResultToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class ConfigOptional {
|
||||||
|
ConfigOptional({
|
||||||
|
this.cookies,
|
||||||
|
this.dbPath,
|
||||||
|
this.ua,
|
||||||
|
this.ex,
|
||||||
|
this.base,
|
||||||
|
this.maxTaskCount,
|
||||||
|
this.mpv,
|
||||||
|
this.maxRetryCount,
|
||||||
|
this.maxDownloadImgCount,
|
||||||
|
this.downloadOriginalImg,
|
||||||
|
this.port,
|
||||||
|
this.exportZipJpnTitle,
|
||||||
|
this.hostname,
|
||||||
|
this.meiliHost,
|
||||||
|
this.meiliSearchApiKey,
|
||||||
|
this.meiliUpdateApiKey,
|
||||||
|
this.ffmpegPath,
|
||||||
|
this.thumbnailMethod,
|
||||||
|
this.thumbnailDir,
|
||||||
|
this.removePreviousGallery,
|
||||||
|
this.imgVerifySecret,
|
||||||
|
this.meiliHosts,
|
||||||
|
this.corsCredentialsHosts,
|
||||||
|
this.flutterFrontend,
|
||||||
|
this.fetchTimeout,
|
||||||
|
this.downloadTimeout,
|
||||||
|
this.ffprobePath,
|
||||||
|
});
|
||||||
|
String? cookies;
|
||||||
|
String? dbPath;
|
||||||
|
String? ua;
|
||||||
|
bool? ex;
|
||||||
|
String? base;
|
||||||
|
int? maxTaskCount;
|
||||||
|
bool? mpv;
|
||||||
|
int? maxRetryCount;
|
||||||
|
int? maxDownloadImgCount;
|
||||||
|
bool? downloadOriginalImg;
|
||||||
|
int? port;
|
||||||
|
bool? exportZipJpnTitle;
|
||||||
|
String? hostname;
|
||||||
|
String? meiliHost;
|
||||||
|
String? meiliSearchApiKey;
|
||||||
|
String? meiliUpdateApiKey;
|
||||||
|
String? ffmpegPath;
|
||||||
|
ThumbnailMethod? thumbnailMethod;
|
||||||
|
String? thumbnailDir;
|
||||||
|
bool? removePreviousGallery;
|
||||||
|
String? imgVerifySecret;
|
||||||
|
Map<String, String>? meiliHosts;
|
||||||
|
List<String>? corsCredentialsHosts;
|
||||||
|
String? flutterFrontend;
|
||||||
|
int? fetchTimeout;
|
||||||
|
int? downloadTimeout;
|
||||||
|
String? ffprobePath;
|
||||||
|
factory ConfigOptional.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ConfigOptionalFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$ConfigOptionalToJson(this);
|
||||||
|
}
|
||||||
154
lib/api/config.g.dart
Normal file
154
lib/api/config.g.dart
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'config.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Config _$ConfigFromJson(Map<String, dynamic> json) => Config(
|
||||||
|
cookies: json['cookies'] as bool,
|
||||||
|
dbPath: json['db_path'] as String?,
|
||||||
|
ua: json['ua'] as String?,
|
||||||
|
ex: json['ex'] as bool,
|
||||||
|
base: json['base'] as String,
|
||||||
|
maxTaskCount: json['max_task_count'] as int,
|
||||||
|
mpv: json['mpv'] as bool,
|
||||||
|
maxRetryCount: json['max_retry_count'] as int,
|
||||||
|
maxDownloadImgCount: json['max_download_img_count'] as int,
|
||||||
|
downloadOriginalImg: json['download_original_img'] as bool,
|
||||||
|
port: json['port'] as int,
|
||||||
|
exportZipJpnTitle: json['export_zip_jpn_title'] as bool,
|
||||||
|
hostname: json['hostname'] as String,
|
||||||
|
meiliHost: json['meili_host'] as String?,
|
||||||
|
meiliSearchApiKey: json['meili_search_api_key'] as String?,
|
||||||
|
meiliUpdateApiKey: json['meili_update_api_key'] as String?,
|
||||||
|
ffmpegPath: json['ffmpeg_path'] as String,
|
||||||
|
thumbnailMethod:
|
||||||
|
$enumDecode(_$ThumbnailMethodEnumMap, json['thumbnail_method']),
|
||||||
|
thumbnailDir: json['thumbnail_dir'] as String,
|
||||||
|
removePreviousGallery: json['remove_previous_gallery'] as bool,
|
||||||
|
imgVerifySecret: json['img_verify_secret'] as String?,
|
||||||
|
meiliHosts: (json['meili_hosts'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, e as String),
|
||||||
|
),
|
||||||
|
corsCredentialsHosts: (json['cors_credentials_hosts'] as List<dynamic>)
|
||||||
|
.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
flutterFrontend: json['flutter_frontend'] as String?,
|
||||||
|
fetchTimeout: json['fetch_timeout'] as int,
|
||||||
|
downloadTimeout: json['download_timeout'] as int,
|
||||||
|
ffprobePath: json['ffprobe_path'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ConfigToJson(Config instance) => <String, dynamic>{
|
||||||
|
'cookies': instance.cookies,
|
||||||
|
'db_path': instance.dbPath,
|
||||||
|
'ua': instance.ua,
|
||||||
|
'ex': instance.ex,
|
||||||
|
'base': instance.base,
|
||||||
|
'max_task_count': instance.maxTaskCount,
|
||||||
|
'mpv': instance.mpv,
|
||||||
|
'max_retry_count': instance.maxRetryCount,
|
||||||
|
'max_download_img_count': instance.maxDownloadImgCount,
|
||||||
|
'download_original_img': instance.downloadOriginalImg,
|
||||||
|
'port': instance.port,
|
||||||
|
'export_zip_jpn_title': instance.exportZipJpnTitle,
|
||||||
|
'hostname': instance.hostname,
|
||||||
|
'meili_host': instance.meiliHost,
|
||||||
|
'meili_search_api_key': instance.meiliSearchApiKey,
|
||||||
|
'meili_update_api_key': instance.meiliUpdateApiKey,
|
||||||
|
'ffmpeg_path': instance.ffmpegPath,
|
||||||
|
'thumbnail_method': _$ThumbnailMethodEnumMap[instance.thumbnailMethod]!,
|
||||||
|
'thumbnail_dir': instance.thumbnailDir,
|
||||||
|
'remove_previous_gallery': instance.removePreviousGallery,
|
||||||
|
'img_verify_secret': instance.imgVerifySecret,
|
||||||
|
'meili_hosts': instance.meiliHosts,
|
||||||
|
'cors_credentials_hosts': instance.corsCredentialsHosts,
|
||||||
|
'flutter_frontend': instance.flutterFrontend,
|
||||||
|
'fetch_timeout': instance.fetchTimeout,
|
||||||
|
'download_timeout': instance.downloadTimeout,
|
||||||
|
'ffprobe_path': instance.ffprobePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ThumbnailMethodEnumMap = {
|
||||||
|
ThumbnailMethod.ffmpegBinary: 0,
|
||||||
|
ThumbnailMethod.ffmpegApi: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
UpdateConfigResult _$UpdateConfigResultFromJson(Map<String, dynamic> json) =>
|
||||||
|
UpdateConfigResult(
|
||||||
|
isUnsafe: json['is_unsafe'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$UpdateConfigResultToJson(UpdateConfigResult instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'is_unsafe': instance.isUnsafe,
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigOptional _$ConfigOptionalFromJson(Map<String, dynamic> json) =>
|
||||||
|
ConfigOptional(
|
||||||
|
cookies: json['cookies'] as String?,
|
||||||
|
dbPath: json['dbPath'] as String?,
|
||||||
|
ua: json['ua'] as String?,
|
||||||
|
ex: json['ex'] as bool?,
|
||||||
|
base: json['base'] as String?,
|
||||||
|
maxTaskCount: json['maxTaskCount'] as int?,
|
||||||
|
mpv: json['mpv'] as bool?,
|
||||||
|
maxRetryCount: json['maxRetryCount'] as int?,
|
||||||
|
maxDownloadImgCount: json['maxDownloadImgCount'] as int?,
|
||||||
|
downloadOriginalImg: json['downloadOriginalImg'] as bool?,
|
||||||
|
port: json['port'] as int?,
|
||||||
|
exportZipJpnTitle: json['exportZipJpnTitle'] as bool?,
|
||||||
|
hostname: json['hostname'] as String?,
|
||||||
|
meiliHost: json['meiliHost'] as String?,
|
||||||
|
meiliSearchApiKey: json['meiliSearchApiKey'] as String?,
|
||||||
|
meiliUpdateApiKey: json['meiliUpdateApiKey'] as String?,
|
||||||
|
ffmpegPath: json['ffmpegPath'] as String?,
|
||||||
|
thumbnailMethod: $enumDecodeNullable(
|
||||||
|
_$ThumbnailMethodEnumMap, json['thumbnailMethod']),
|
||||||
|
thumbnailDir: json['thumbnailDir'] as String?,
|
||||||
|
removePreviousGallery: json['removePreviousGallery'] as bool?,
|
||||||
|
imgVerifySecret: json['imgVerifySecret'] as String?,
|
||||||
|
meiliHosts: (json['meiliHosts'] as Map<String, dynamic>?)?.map(
|
||||||
|
(k, e) => MapEntry(k, e as String),
|
||||||
|
),
|
||||||
|
corsCredentialsHosts: (json['corsCredentialsHosts'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
flutterFrontend: json['flutterFrontend'] as String?,
|
||||||
|
fetchTimeout: json['fetchTimeout'] as int?,
|
||||||
|
downloadTimeout: json['downloadTimeout'] as int?,
|
||||||
|
ffprobePath: json['ffprobePath'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$ConfigOptionalToJson(ConfigOptional instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'cookies': instance.cookies,
|
||||||
|
'dbPath': instance.dbPath,
|
||||||
|
'ua': instance.ua,
|
||||||
|
'ex': instance.ex,
|
||||||
|
'base': instance.base,
|
||||||
|
'maxTaskCount': instance.maxTaskCount,
|
||||||
|
'mpv': instance.mpv,
|
||||||
|
'maxRetryCount': instance.maxRetryCount,
|
||||||
|
'maxDownloadImgCount': instance.maxDownloadImgCount,
|
||||||
|
'downloadOriginalImg': instance.downloadOriginalImg,
|
||||||
|
'port': instance.port,
|
||||||
|
'exportZipJpnTitle': instance.exportZipJpnTitle,
|
||||||
|
'hostname': instance.hostname,
|
||||||
|
'meiliHost': instance.meiliHost,
|
||||||
|
'meiliSearchApiKey': instance.meiliSearchApiKey,
|
||||||
|
'meiliUpdateApiKey': instance.meiliUpdateApiKey,
|
||||||
|
'ffmpegPath': instance.ffmpegPath,
|
||||||
|
'thumbnailMethod': _$ThumbnailMethodEnumMap[instance.thumbnailMethod],
|
||||||
|
'thumbnailDir': instance.thumbnailDir,
|
||||||
|
'removePreviousGallery': instance.removePreviousGallery,
|
||||||
|
'imgVerifySecret': instance.imgVerifySecret,
|
||||||
|
'meiliHosts': instance.meiliHosts,
|
||||||
|
'corsCredentialsHosts': instance.corsCredentialsHosts,
|
||||||
|
'flutterFrontend': instance.flutterFrontend,
|
||||||
|
'fetchTimeout': instance.fetchTimeout,
|
||||||
|
'downloadTimeout': instance.downloadTimeout,
|
||||||
|
'ffprobePath': instance.ffprobePath,
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ class AuthInfo {
|
|||||||
bool get checked => _checked;
|
bool get checked => _checked;
|
||||||
bool _isChecking = false;
|
bool _isChecking = false;
|
||||||
bool get isChecking => _isChecking;
|
bool get isChecking => _isChecking;
|
||||||
|
bool? get isAdmin => _user?.isAdmin;
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
_user = null;
|
_user = null;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ enum MoreVertSettings {
|
|||||||
settings,
|
settings,
|
||||||
markAsNsfw,
|
markAsNsfw,
|
||||||
markAsSfw,
|
markAsSfw,
|
||||||
|
serverSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
|
void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
|
||||||
@@ -155,6 +156,9 @@ void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
|
|||||||
case MoreVertSettings.markAsSfw:
|
case MoreVertSettings.markAsSfw:
|
||||||
GalleryPage.maybeOf(context)?.markGalleryAsNsfw(false);
|
GalleryPage.maybeOf(context)?.markGalleryAsNsfw(false);
|
||||||
break;
|
break;
|
||||||
|
case MoreVertSettings.serverSettings:
|
||||||
|
context.push("/server_settings");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -183,6 +187,11 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
|
|||||||
value: MoreVertSettings.settings,
|
value: MoreVertSettings.settings,
|
||||||
child: Text(AppLocalizations.of(context)!.settings)));
|
child: Text(AppLocalizations.of(context)!.settings)));
|
||||||
}
|
}
|
||||||
|
if (path != "/server_settings" && auth.isAdmin == true) {
|
||||||
|
list.add(PopupMenuItem(
|
||||||
|
value: MoreVertSettings.serverSettings,
|
||||||
|
child: Text(AppLocalizations.of(context)!.serverSettings)));
|
||||||
|
}
|
||||||
var showNsfw = prefs.getBool("showNsfw") ?? false;
|
var showNsfw = prefs.getBool("showNsfw") ?? false;
|
||||||
list.add(PopupMenuItem(
|
list.add(PopupMenuItem(
|
||||||
child: StatefulBuilder(
|
child: StatefulBuilder(
|
||||||
|
|||||||
@@ -91,5 +91,11 @@
|
|||||||
"markAsNsfw": "Mark as NSFW",
|
"markAsNsfw": "Mark as NSFW",
|
||||||
"markAsSfw": "Mark as SFW",
|
"markAsSfw": "Mark as SFW",
|
||||||
"markAsAd": "Mark as Ad",
|
"markAsAd": "Mark as Ad",
|
||||||
"markAsNonAd": "Mark as non-Ad"
|
"markAsNonAd": "Mark as non-Ad",
|
||||||
|
"serverSettings": "Server Settings",
|
||||||
|
"useEx": "Use exhentai.org.",
|
||||||
|
"mpv": "Fetch page data from Multi-Page Viewer.",
|
||||||
|
"downloadOriginalImg": "Download original images.",
|
||||||
|
"exportZipJpnTitle": "Use japanese title first when exporting zip.",
|
||||||
|
"removePreviousGallery": "Remove old galleries which replaced by new ones."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,5 +91,11 @@
|
|||||||
"markAsNsfw": "标记为NSFW",
|
"markAsNsfw": "标记为NSFW",
|
||||||
"markAsSfw": "标记为SFW",
|
"markAsSfw": "标记为SFW",
|
||||||
"markAsAd": "标记为广告",
|
"markAsAd": "标记为广告",
|
||||||
"markAsNonAd": "标记为非广告"
|
"markAsNonAd": "标记为非广告",
|
||||||
|
"serverSettings": "服务器设置",
|
||||||
|
"useEx": "使用 exhentai.org。",
|
||||||
|
"mpv": "从 Multi-Page Viewer 获取页面数据。",
|
||||||
|
"downloadOriginalImg": "下载原始画质的图片。",
|
||||||
|
"exportZipJpnTitle": "导出Zip时优先使用日语标题。",
|
||||||
|
"removePreviousGallery": "移除被新画廊替代的旧画廊。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import 'globals.dart';
|
|||||||
import 'home.dart';
|
import 'home.dart';
|
||||||
import 'login.dart';
|
import 'login.dart';
|
||||||
import 'logs/file.dart';
|
import 'logs/file.dart';
|
||||||
|
import 'server_settings.dart';
|
||||||
import 'set_server.dart';
|
import 'set_server.dart';
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
@@ -156,6 +157,10 @@ final _router = GoRouter(
|
|||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
GoRoute(
|
||||||
|
path: ServerSettingsPage.routeName,
|
||||||
|
builder: (context, state) => const ServerSettingsPage(),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
271
lib/server_settings.dart
Normal file
271
lib/server_settings.dart
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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 'globals.dart';
|
||||||
|
|
||||||
|
final _log = Logger("ServerSettingsPage");
|
||||||
|
|
||||||
|
class ServerSettingsPage extends StatefulWidget {
|
||||||
|
const ServerSettingsPage({Key? key}) : super(key: key);
|
||||||
|
static get routeName => "/server_settings";
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ServerSettingsPage> createState() => _ServerSettingsPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ServerSettingsPage extends State<ServerSettingsPage>
|
||||||
|
with IsTopWidget2, ThemeModeWidget {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late bool _isLoading;
|
||||||
|
late bool _isSaving;
|
||||||
|
late bool _changed;
|
||||||
|
late ScrollController _controller;
|
||||||
|
late ConfigOptional _now;
|
||||||
|
Config? _config;
|
||||||
|
Object? _error;
|
||||||
|
CancelToken? _cancel;
|
||||||
|
CancelToken? _saveCancel;
|
||||||
|
|
||||||
|
Future<void> _fetchData() async {
|
||||||
|
_cancel = CancelToken();
|
||||||
|
try {
|
||||||
|
final config = await api.getConfig(cancel: _cancel);
|
||||||
|
if (!_cancel!.isCancelled) {
|
||||||
|
setState(() {
|
||||||
|
_config = config;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!_cancel!.isCancelled) {
|
||||||
|
_log.warning("Error when fetching config:", e);
|
||||||
|
setState(() {
|
||||||
|
_error = e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveConfig() async {
|
||||||
|
if (_isSaving) return;
|
||||||
|
try {
|
||||||
|
_saveCancel = CancelToken();
|
||||||
|
setState(() {
|
||||||
|
_isSaving = true;
|
||||||
|
});
|
||||||
|
await api.updateConfig(_now, cancel: _saveCancel);
|
||||||
|
if (!_saveCancel!.isCancelled) {
|
||||||
|
setState(() {
|
||||||
|
_isSaving = false;
|
||||||
|
_now = ConfigOptional();
|
||||||
|
_changed = false;
|
||||||
|
_config = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!_saveCancel!.isCancelled) {
|
||||||
|
_log.warning("Error when saving config:", e);
|
||||||
|
setState(() {
|
||||||
|
_isSaving = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_isLoading = false;
|
||||||
|
_isSaving = false;
|
||||||
|
_changed = false;
|
||||||
|
_controller = ScrollController();
|
||||||
|
_now = ConfigOptional();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_cancel?.cancel();
|
||||||
|
_formKey.currentState?.dispose();
|
||||||
|
_controller.dispose();
|
||||||
|
_saveCancel?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!tryInitApi(context)) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
final isLoading = _config == null && _error == null;
|
||||||
|
if (isLoading && !_isLoading) _fetchData();
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
if (isTop(context)) {
|
||||||
|
setCurrentTitle(i18n.serverSettings, cs.primary.value);
|
||||||
|
}
|
||||||
|
return Scaffold(
|
||||||
|
appBar: isLoading
|
||||||
|
? AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
context.canPop() ? context.pop() : context.go("/");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(i18n.serverSettings),
|
||||||
|
actions: [
|
||||||
|
buildThemeModeIcon(context),
|
||||||
|
buildMoreVertSettingsButon(context),
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
body: isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _error != null
|
||||||
|
? Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SelectableText("Error $_error"),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_fetchData();
|
||||||
|
setState(() {
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
label: Text(AppLocalizations.of(context)!.retry))
|
||||||
|
]))
|
||||||
|
: _buildForm(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildForm(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
return Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: CustomScrollView(
|
||||||
|
controller: _controller,
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
context.canPop() ? context.pop() : context.go("/");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(i18n.serverSettings),
|
||||||
|
actions: [
|
||||||
|
buildThemeModeIcon(context),
|
||||||
|
buildMoreVertSettingsButon(context),
|
||||||
|
]),
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildListDelegate([
|
||||||
|
_buildCheckBox(context),
|
||||||
|
_buildBottomBar(context),
|
||||||
|
])),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWithHorizontalPadding(BuildContext context, Widget child) {
|
||||||
|
return Container(
|
||||||
|
padding: MediaQuery.of(context).size.width > 810
|
||||||
|
? const EdgeInsets.symmetric(horizontal: 100)
|
||||||
|
: null,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWithVecticalPadding(Widget child) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCheckBox(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
return _buildWithHorizontalPadding(
|
||||||
|
context,
|
||||||
|
Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
_buildWithVecticalPadding(CheckboxMenuButton(
|
||||||
|
value: _now.ex ?? _config!.ex,
|
||||||
|
onChanged: (b) {
|
||||||
|
if (b != null) {
|
||||||
|
setState(() {
|
||||||
|
_now.ex = b;
|
||||||
|
_changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.useEx))),
|
||||||
|
_buildWithVecticalPadding(CheckboxMenuButton(
|
||||||
|
value: _now.mpv ?? _config!.mpv,
|
||||||
|
onChanged: (b) {
|
||||||
|
if (b != null) {
|
||||||
|
setState(() {
|
||||||
|
_now.mpv = b;
|
||||||
|
_changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.mpv))),
|
||||||
|
_buildWithVecticalPadding(CheckboxMenuButton(
|
||||||
|
value: _now.downloadOriginalImg ?? _config!.downloadOriginalImg,
|
||||||
|
onChanged: (b) {
|
||||||
|
if (b != null) {
|
||||||
|
setState(() {
|
||||||
|
_now.downloadOriginalImg = b;
|
||||||
|
_changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.downloadOriginalImg))),
|
||||||
|
_buildWithVecticalPadding(CheckboxMenuButton(
|
||||||
|
value: _now.exportZipJpnTitle ?? _config!.exportZipJpnTitle,
|
||||||
|
onChanged: (b) {
|
||||||
|
if (b != null) {
|
||||||
|
setState(() {
|
||||||
|
_now.exportZipJpnTitle = b;
|
||||||
|
_changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.exportZipJpnTitle))),
|
||||||
|
_buildWithVecticalPadding(CheckboxMenuButton(
|
||||||
|
value:
|
||||||
|
_now.removePreviousGallery ?? _config!.removePreviousGallery,
|
||||||
|
onChanged: (b) {
|
||||||
|
if (b != null) {
|
||||||
|
setState(() {
|
||||||
|
_now.removePreviousGallery = b;
|
||||||
|
_changed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(i18n.removePreviousGallery))),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBottomBar(BuildContext context) {
|
||||||
|
final i18n = AppLocalizations.of(context)!;
|
||||||
|
return _buildWithHorizontalPadding(
|
||||||
|
context,
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
_buildWithVecticalPadding(ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
label: Text(i18n.save),
|
||||||
|
onPressed: _changed
|
||||||
|
? () {
|
||||||
|
_saveConfig();
|
||||||
|
}
|
||||||
|
: null)),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user