mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-03-11 17:25:32 +00:00
enhanced repo manager
- added custom DNS setting
This commit is contained in:
parent
7eba7bdcf2
commit
74c5eab379
31 changed files with 1172 additions and 558 deletions
|
|
@ -443,6 +443,7 @@
|
|||
"manga_extensions_repo": "Manga extensions repo",
|
||||
"anime_extensions_repo": "Anime extensions repo",
|
||||
"novel_extensions_repo": "Novel extensions repo",
|
||||
"custom_dns": "Custom DNS (leave blank to use system DNS)",
|
||||
"android_proxy_server": "Android Proxy Server (ApkBridge)",
|
||||
"undefined": "undefined",
|
||||
"empty_extensions_repo": "You don't have any repository urls here. Click on the plus button to add one!",
|
||||
|
|
|
|||
|
|
@ -2733,6 +2733,12 @@ abstract class AppLocalizations {
|
|||
/// **'Novel extensions repo'**
|
||||
String get novel_extensions_repo;
|
||||
|
||||
/// No description provided for @custom_dns.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom DNS (leave blank to use system DNS)'**
|
||||
String get custom_dns;
|
||||
|
||||
/// No description provided for @android_proxy_server.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -1402,6 +1402,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'مستودع إضافات الروايات';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1404,6 +1404,9 @@ class AppLocalizationsAs extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Novel extensions repo';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1413,6 +1413,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Roman-Erweiterungs-Repository';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1403,6 +1403,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Novel extensions repo';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1417,6 +1417,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Repositorio de extensiones de novelas';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1420,6 +1420,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Dépôt d\'extensions de romans';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1405,6 +1405,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Novel extensions repo';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1409,6 +1409,9 @@ class AppLocalizationsId extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Repositori ekstensi novel';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1417,6 +1417,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Repository delle estensioni romanzi';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1414,6 +1414,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Repositório de extensões de romances';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1416,6 +1416,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Репозиторий расширений новелл';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1403,6 +1403,9 @@ class AppLocalizationsTh extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'ที่เก็บส่วนขยายโนเวล';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1409,6 +1409,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => 'Roman uzantıları deposu';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -1377,6 +1377,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get novel_extensions_repo => '小说扩展库';
|
||||
|
||||
@override
|
||||
String get custom_dns => 'Custom DNS (leave blank to use system DNS)';
|
||||
|
||||
@override
|
||||
String get android_proxy_server => 'Android Proxy Server (ApkBridge)';
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:mangayomi/models/source.dart';
|
|||
import 'package:mangayomi/models/track_search.dart';
|
||||
import 'package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
import 'package:mangayomi/modules/more/settings/general/providers/general_state_provider.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/providers/storage_provider.dart';
|
||||
import 'package:mangayomi/router/router.dart';
|
||||
|
|
@ -38,6 +39,7 @@ import 'package:path/path.dart' as p;
|
|||
late Isar isar;
|
||||
DiscordRPC? discordRpc;
|
||||
WebViewEnvironment? webViewEnvironment;
|
||||
String? customDns;
|
||||
void main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (Platform.isLinux && runWebViewTitleBarWidget(args)) return;
|
||||
|
|
@ -97,6 +99,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
|||
unawaited(ref.read(scanLocalLibraryProvider.future));
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
customDns = ref.read(customDnsStateProvider);
|
||||
if (ref.read(clearChapterCacheOnAppLaunchStateProvider)) {
|
||||
ref
|
||||
.read(totalChapterCacheSizeStateProvider.notifier)
|
||||
|
|
|
|||
|
|
@ -169,6 +169,8 @@ class Settings {
|
|||
|
||||
int? aniSkipTimeoutLength;
|
||||
|
||||
String? customDns;
|
||||
|
||||
String? btServerAddress;
|
||||
|
||||
int? btServerPort;
|
||||
|
|
@ -261,9 +263,8 @@ class Settings {
|
|||
bool? rpcShowCoverImage;
|
||||
|
||||
bool? downloadedOnlyMode;
|
||||
|
||||
late AlgorithmWeights? algorithmWeights;
|
||||
|
||||
late AlgorithmWeights? algorithmWeights;
|
||||
|
||||
Settings({
|
||||
this.id = 227,
|
||||
|
|
@ -339,6 +340,7 @@ class Settings {
|
|||
this.enableAniSkip,
|
||||
this.enableAutoSkip,
|
||||
this.aniSkipTimeoutLength,
|
||||
this.customDns = "",
|
||||
this.btServerAddress = "127.0.0.1",
|
||||
this.btServerPort,
|
||||
this.fullScreenReader = true,
|
||||
|
|
@ -526,6 +528,7 @@ class Settings {
|
|||
enableAniSkip = json['enableAniSkip'];
|
||||
enableAutoSkip = json['enableAutoSkip'];
|
||||
aniSkipTimeoutLength = json['aniSkipTimeoutLength'];
|
||||
customDns = json['customDns'];
|
||||
btServerAddress = json['btServerAddress'];
|
||||
btServerPort = json['btServerPort'];
|
||||
customColorFilter = json['customColorFilter'] != null
|
||||
|
|
@ -703,6 +706,7 @@ class Settings {
|
|||
'enableAniSkip': enableAniSkip,
|
||||
'enableAutoSkip': enableAutoSkip,
|
||||
'aniSkipTimeoutLength': aniSkipTimeoutLength,
|
||||
'customDns': customDns,
|
||||
'btServerAddress': btServerAddress,
|
||||
'btServerPort': btServerPort,
|
||||
'fullScreenReader': fullScreenReader,
|
||||
|
|
@ -937,20 +941,34 @@ class Repo {
|
|||
String? name;
|
||||
String? website;
|
||||
String? jsonUrl;
|
||||
bool? hidden;
|
||||
|
||||
Repo({this.name, this.website, this.jsonUrl});
|
||||
Repo({this.name, this.website, this.jsonUrl, this.hidden});
|
||||
|
||||
Repo.fromJson(Map<String, dynamic> json) {
|
||||
name = json['name'];
|
||||
website = json['website'];
|
||||
name = json['meta']?['name'] ?? json['name'];
|
||||
website = json['meta']?['website'] ?? json['website'];
|
||||
jsonUrl = json['jsonUrl'];
|
||||
hidden = json['hidden'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'website': website,
|
||||
'jsonUrl': jsonUrl,
|
||||
'hidden': hidden,
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is Repo &&
|
||||
name == other.name &&
|
||||
website == other.website &&
|
||||
jsonUrl == other.jsonUrl;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(name, website, jsonUrl);
|
||||
}
|
||||
|
||||
@embedded
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_qjs/quickjs/ffi.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/custom_sliver_grouped_list_view.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
|
|
@ -66,6 +68,9 @@ class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
|
|||
final streamExtensions = ref.watch(
|
||||
getExtensionsStreamProvider(widget.itemType),
|
||||
);
|
||||
final repositories = ref.watch(
|
||||
extensionsRepoStateProvider(widget.itemType),
|
||||
);
|
||||
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
|
||||
|
|
@ -92,6 +97,12 @@ class _ExtensionScreenState extends ConsumerState<ExtensionScreen> {
|
|||
final notInstalledEntries = <Source>[];
|
||||
|
||||
for (var element in filteredData) {
|
||||
if (repositories
|
||||
.firstWhereOrNull((e) => e == element.repo)
|
||||
?.hidden ??
|
||||
false) {
|
||||
continue;
|
||||
}
|
||||
final isLatestVersion = element.version == element.versionLast;
|
||||
|
||||
if (compareVersions(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:mangayomi/main.dart';
|
||||
import 'package:mangayomi/models/manga.dart';
|
||||
import 'package:mangayomi/models/settings.dart';
|
||||
import 'package:mangayomi/models/source.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -12,6 +13,7 @@ Stream<List<Source>> getExtensionsStream(Ref ref, ItemType itemType) async* {
|
|||
.filter()
|
||||
.idIsNotNull()
|
||||
.and()
|
||||
.repo((q) => q.hiddenIsNull().or().hiddenEqualTo(false))
|
||||
.isActiveEqualTo(true)
|
||||
.itemTypeEqualTo(itemType)
|
||||
.watch(fireImmediately: true);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'extensions_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$getExtensionsStreamHash() =>
|
||||
r'3c5d6625c40c222f25fc8141df078dd46bcc762f';
|
||||
r'af34092ebf31c784010110af746e3ee2731297bd';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$updateMangaDetailHash() => r'30185777f73eaf9eac4cce554a7ecbc9e1c0b613';
|
||||
String _$updateMangaDetailHash() => r'44a7da7d5ef698b030c98fd5439cf9e7525350ef';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,16 @@ class ExtensionsRepoState extends _$ExtensionsRepoState {
|
|||
[];
|
||||
}
|
||||
|
||||
void setVisibility(Repo repo, bool hidden) {
|
||||
final value = state.map((e) {
|
||||
if (e == repo) {
|
||||
e.hidden = hidden;
|
||||
}
|
||||
return e;
|
||||
}).toList();
|
||||
set(value);
|
||||
}
|
||||
|
||||
void set(List<Repo> value) {
|
||||
final settings = isar.settings.getSync(227)!;
|
||||
state = value;
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ final onlyIncludePinnedSourceStateProvider =
|
|||
|
||||
typedef _$OnlyIncludePinnedSourceState = AutoDisposeNotifier<bool>;
|
||||
String _$extensionsRepoStateHash() =>
|
||||
r'5c23b8b7ecf83b253b76a2663a71c0c752e53a40';
|
||||
r'86edc9a3f78d72acda4b20a058031c345ee406eb';
|
||||
|
||||
abstract class _$ExtensionsRepoState
|
||||
extends BuildlessAutoDisposeNotifier<List<Repo>> {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import 'package:mangayomi/models/settings.dart';
|
|||
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
|
||||
import 'package:mangayomi/modules/widgets/progress_center.dart';
|
||||
import 'package:mangayomi/providers/l10n_providers.dart';
|
||||
import 'package:mangayomi/services/fetch_item_sources.dart';
|
||||
import 'package:mangayomi/utils/cached_network.dart';
|
||||
import 'package:super_sliver_list/super_sliver_list.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
|
|
@ -19,8 +21,13 @@ class SourceRepositories extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
||||
final urlRegex = RegExp(
|
||||
r'^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$',
|
||||
);
|
||||
List<Repo> _entries = [];
|
||||
String urlInput = "";
|
||||
bool isRefreshing = false;
|
||||
|
||||
Future<void> _launchInBrowser(Uri url) async {
|
||||
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
||||
throw 'Could not launch $url';
|
||||
|
|
@ -41,6 +48,43 @@ class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
|||
ItemType.anime => Text(l10n.manage_anime_repo_urls),
|
||||
_ => Text(l10n.manage_novel_repo_urls),
|
||||
},
|
||||
actions: [
|
||||
isRefreshing
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(20.0),
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 3),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: IconButton(
|
||||
splashRadius: 20,
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
isRefreshing = true;
|
||||
});
|
||||
final result = await ref.refresh(
|
||||
fetchItemSourcesListProvider(
|
||||
id: null,
|
||||
reFresh: true,
|
||||
itemType: widget.itemType,
|
||||
).future,
|
||||
);
|
||||
setState(() {
|
||||
isRefreshing = false;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.refresh,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: data.when(
|
||||
data: (data) {
|
||||
|
|
@ -62,6 +106,12 @@ class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
|||
itemCount: _entries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final repo = _entries[index];
|
||||
final isHidden = repo.hidden ?? false;
|
||||
final repoAvatar = urlRegex
|
||||
.firstMatch(repo.jsonUrl ?? "")
|
||||
?.group(4)
|
||||
?.split("/")
|
||||
.elementAtOrNull(1);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Card(
|
||||
|
|
@ -69,25 +119,53 @@ class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 15,
|
||||
Opacity(
|
||||
opacity: isHidden ? 0.3 : 1,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (repoAvatar != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: cachedNetworkImage(
|
||||
imageUrl:
|
||||
"https://github.com/$repoAvatar.png?size=64",
|
||||
fit: BoxFit.contain,
|
||||
width: 64,
|
||||
height: 64,
|
||||
errorWidget: const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 15,
|
||||
),
|
||||
child: Icon(Icons.label_outline_rounded),
|
||||
),
|
||||
useCustomNetworkImage: false,
|
||||
),
|
||||
),
|
||||
if (repoAvatar == null)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 15,
|
||||
),
|
||||
child: Icon(Icons.label_outline_rounded),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
repo.name ??
|
||||
repo.jsonUrl ??
|
||||
"Invalid source - remove it",
|
||||
style: TextStyle(
|
||||
decoration: isHidden
|
||||
? TextDecoration.lineThrough
|
||||
: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Icon(Icons.label_outline_rounded),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
repo.name ??
|
||||
repo.jsonUrl ??
|
||||
"Invalid source - remove it",
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
|
|
@ -112,72 +190,40 @@ class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
|||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
l10n.remove_extensions_repo,
|
||||
),
|
||||
content: Text(
|
||||
l10n.remove_extensions_repo,
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final mangaRepos = ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
mangaRepos.removeWhere(
|
||||
(url) =>
|
||||
url ==
|
||||
_entries[index],
|
||||
);
|
||||
ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
).notifier,
|
||||
)
|
||||
.set(mangaRepos);
|
||||
ref.watch(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
onPressed: () => ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
).notifier,
|
||||
)
|
||||
.setVisibility(repo, !isHidden),
|
||||
icon: Stack(
|
||||
children: [
|
||||
const Icon(Icons.remove_red_eye_outlined),
|
||||
if (!isHidden)
|
||||
Positioned(
|
||||
right: 8,
|
||||
child: Transform.scale(
|
||||
scaleX: 2.5,
|
||||
child: const Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'\\',
|
||||
style: TextStyle(fontSize: 17),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
_showRemoveRepoDialog(context, index),
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
],
|
||||
|
|
@ -207,144 +253,7 @@ class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
|||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
bool isLoading = false;
|
||||
final controller = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SizedBox(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.add_extensions_repo),
|
||||
content: TextFormField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.url,
|
||||
onChanged: (value) => setState(() {}),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.url_cannot_be_empty;
|
||||
}
|
||||
if (!value.endsWith('.json')) {
|
||||
return l10n.url_must_end_with_dot_json;
|
||||
}
|
||||
try {
|
||||
final uri = Uri.parse(value);
|
||||
if (!uri.isAbsolute) {
|
||||
return l10n.invalid_url_format;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return l10n.invalid_url_format;
|
||||
}
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.url_must_end_with_dot_json,
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide(),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return TextButton(
|
||||
onPressed:
|
||||
controller.text.isEmpty ||
|
||||
!controller.text.endsWith(".json")
|
||||
? null
|
||||
: () async {
|
||||
setState(() => isLoading = true);
|
||||
try {
|
||||
final mangaRepos = ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final repo = await ref.read(
|
||||
getRepoInfosProvider(
|
||||
jsonUrl: controller.text,
|
||||
).future,
|
||||
);
|
||||
if (repo == null) {
|
||||
botToast(l10n.unsupported_repo);
|
||||
return;
|
||||
}
|
||||
mangaRepos.add(repo);
|
||||
ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
).notifier,
|
||||
)
|
||||
.set(mangaRepos);
|
||||
} catch (e, s) {
|
||||
setState(() => isLoading = false);
|
||||
botToast('$e\n$s');
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: Text(
|
||||
l10n.add,
|
||||
style: TextStyle(
|
||||
color:
|
||||
controller.text.isEmpty ||
|
||||
!controller.text.endsWith(
|
||||
".json",
|
||||
)
|
||||
? Theme.of(context).primaryColor
|
||||
.withValues(alpha: 0.2)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onPressed: () => _showAddRepoDialog(context),
|
||||
label: Row(
|
||||
children: [
|
||||
const Icon(Icons.add),
|
||||
|
|
@ -355,4 +264,193 @@ class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
_showRemoveRepoDialog(BuildContext context, int index) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
final l10n = context.l10n;
|
||||
return AlertDialog(
|
||||
title: Text(l10n.remove_extensions_repo),
|
||||
content: Text(l10n.remove_extensions_repo),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final mangaRepos = ref
|
||||
.read(extensionsRepoStateProvider(widget.itemType))
|
||||
.toList();
|
||||
mangaRepos.removeWhere((url) => url == _entries[index]);
|
||||
ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
).notifier,
|
||||
)
|
||||
.set(mangaRepos);
|
||||
ref.watch(extensionsRepoStateProvider(widget.itemType));
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_showAddRepoDialog(BuildContext context) {
|
||||
bool isLoading = false;
|
||||
final controller = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SizedBox(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
final l10n = context.l10n;
|
||||
return AlertDialog(
|
||||
title: Text(l10n.add_extensions_repo),
|
||||
content: TextFormField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.url,
|
||||
onChanged: (value) => setState(() {}),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return l10n.url_cannot_be_empty;
|
||||
}
|
||||
if (!value.endsWith('.json')) {
|
||||
return l10n.url_must_end_with_dot_json;
|
||||
}
|
||||
try {
|
||||
final uri = Uri.parse(value);
|
||||
if (!uri.isAbsolute) {
|
||||
return l10n.invalid_url_format;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return l10n.invalid_url_format;
|
||||
}
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.url_must_end_with_dot_json,
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide(),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return TextButton(
|
||||
onPressed:
|
||||
controller.text.isEmpty ||
|
||||
!controller.text.endsWith(".json")
|
||||
? null
|
||||
: () async {
|
||||
setState(() => isLoading = true);
|
||||
try {
|
||||
final mangaRepos = ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final repo = await ref.read(
|
||||
getRepoInfosProvider(
|
||||
jsonUrl: controller.text,
|
||||
).future,
|
||||
);
|
||||
if (repo == null) {
|
||||
botToast(l10n.unsupported_repo);
|
||||
return;
|
||||
}
|
||||
mangaRepos.add(repo);
|
||||
ref
|
||||
.read(
|
||||
extensionsRepoStateProvider(
|
||||
widget.itemType,
|
||||
).notifier,
|
||||
)
|
||||
.set(mangaRepos);
|
||||
} catch (e, s) {
|
||||
setState(() => isLoading = false);
|
||||
botToast('$e\n$s');
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: Text(
|
||||
l10n.add,
|
||||
style: TextStyle(
|
||||
color:
|
||||
controller.text.isEmpty ||
|
||||
!controller.text.endsWith(".json")
|
||||
? Theme.of(context).primaryColor
|
||||
.withValues(alpha: 0.2)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class _GeneralStateScreen extends ConsumerState<GeneralScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = l10nLocalizations(context);
|
||||
final customDns = ref.watch(customDnsStateProvider);
|
||||
final enableDiscordRpc = ref.watch(enableDiscordRpcStateProvider);
|
||||
final hideDiscordRpcInIncognito = ref.watch(
|
||||
hideDiscordRpcInIncognitoStateProvider,
|
||||
|
|
@ -48,6 +49,14 @@ class _GeneralStateScreen extends ConsumerState<GeneralScreen> {
|
|||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () => _showCustomDnsDialog(context, ref, customDns),
|
||||
title: Text(l10n.custom_dns),
|
||||
subtitle: Text(
|
||||
customDns,
|
||||
style: TextStyle(fontSize: 11, color: context.secondaryColor),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.all(20.0),
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
|
|
@ -290,4 +299,77 @@ class _GeneralStateScreen extends ConsumerState<GeneralScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCustomDnsDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
String customDns,
|
||||
) {
|
||||
final dnsController = TextEditingController(text: customDns);
|
||||
String dns = customDns;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
context.l10n.custom_dns,
|
||||
style: const TextStyle(fontSize: 30),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: context.width(0.8),
|
||||
height: context.height(0.3),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: TextFormField(
|
||||
controller: dnsController,
|
||||
autofocus: true,
|
||||
onChanged: (value) => setState(() {
|
||||
dns = value;
|
||||
}),
|
||||
decoration: InputDecoration(
|
||||
hintText: "8.8.8.8",
|
||||
filled: false,
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(width: 0.4),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: const BorderSide(),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: const BorderSide(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: SizedBox(
|
||||
width: context.width(1),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
ref.read(customDnsStateProvider.notifier).set(dns);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(context.l10n.dialog_confirm),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,26 @@ import 'package:mangayomi/models/settings.dart';
|
|||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
part 'general_state_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
class CustomDnsState extends _$CustomDnsState {
|
||||
@override
|
||||
String build() {
|
||||
return isar.settings.getSync(227)!.customDns ?? "";
|
||||
}
|
||||
|
||||
void set(String value) {
|
||||
final settings = isar.settings.getSync(227);
|
||||
state = value;
|
||||
isar.writeTxnSync(
|
||||
() => isar.settings.putSync(
|
||||
settings!
|
||||
..customDns = value
|
||||
..updatedAt = DateTime.now().millisecondsSinceEpoch,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class EnableDiscordRpcState extends _$EnableDiscordRpcState {
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -6,6 +6,22 @@ part of 'general_state_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$customDnsStateHash() => r'6061c64d742b3f873e54c1b9ef724b7c0b6350a2';
|
||||
|
||||
/// See also [CustomDnsState].
|
||||
@ProviderFor(CustomDnsState)
|
||||
final customDnsStateProvider =
|
||||
AutoDisposeNotifierProvider<CustomDnsState, String>.internal(
|
||||
CustomDnsState.new,
|
||||
name: r'customDnsStateProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$customDnsStateHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$CustomDnsState = AutoDisposeNotifier<String>;
|
||||
String _$enableDiscordRpcStateHash() =>
|
||||
r'ab8ce3b29f5d94aedbc88dcb87c7c834648270f5';
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'get_video_list.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getVideoListHash() => r'd39e325f21e68830c0692101e6efd95b2e04bcef';
|
||||
String _$getVideoListHash() => r'a7f0b9549bc2c3b4ddb68850f13da57081d8d002';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -54,8 +54,20 @@ class MClient {
|
|||
rhttp.ClientSettings? settings,
|
||||
bool showCloudFlareError = true,
|
||||
}) {
|
||||
final clientSettings = customDns == null
|
||||
? settings
|
||||
: settings?.copyWith(
|
||||
dnsSettings: DnsSettings.dynamic(
|
||||
resolver: (host) async => [customDns!],
|
||||
),
|
||||
) ??
|
||||
ClientSettings(
|
||||
dnsSettings: DnsSettings.dynamic(
|
||||
resolver: (host) async => [customDns!],
|
||||
),
|
||||
);
|
||||
return InterceptedClient.build(
|
||||
client: httpClient(settings: settings, reqcopyWith: reqcopyWith),
|
||||
client: httpClient(settings: clientSettings, reqcopyWith: reqcopyWith),
|
||||
retryPolicy: ResolveCloudFlareChallenge(showCloudFlareError),
|
||||
interceptors: [
|
||||
MCookieManager(reqcopyWith),
|
||||
|
|
|
|||
Loading…
Reference in a new issue