co-written with @Schnitzel5 : update source fetching methods to utilize repositories

This commit is contained in:
Moustapha Kodjo Amadou 2025-02-12 11:04:46 +01:00
parent f125f442c7
commit 8384bcad51
21 changed files with 2507 additions and 522 deletions

View file

@ -209,10 +209,24 @@
"sync_upload_failed": "Hochladen fehlgeschlagen",
"sync_download_failed": "Herunterladen fehlgeschlagen",
"sync_button_sync": "Jetzt synchronisieren",
"sync_button_snapshot": "Snapshot erstellen",
"sync_button_upload": "Alles hochladen",
"sync_button_download": "Alles herunterladen",
"sync_confirm_snapshot": "Erstelle eine Kopie des derzeiten Backups auf den Server!",
"sync_confirm_upload": "Deine Daten auf dem Server werden jetzt durch deinen lokalen Daten ersetzt!",
"sync_confirm_download": "Deine lokalen Daten werden jetzt durch den Daten vom Server ersetzt!",
"sync_on": "Sync aktivieren",
"sync_pending_manga": "Ausstehende Änderungen für Manga",
"sync_pending_category": "Ausstehende Änderungen für Kategorien",
"sync_pending_chapter": "Ausstehende Änderungen für Kapiteln",
"sync_pending_history": "Ausstehende Änderungen für Fortschritte",
"sync_pending_update": "Ausstehende Änderungen für Updates",
"sync_pending_extension": "Ausstehende Änderungen für Erweiterungen",
"sync_pending_track": "Ausstehende Änderungen für Trackings",
"sync_snapshot_creating": "Erstelle Snapshot...",
"sync_snapshot_created": "Snapshot wurde erstellt!",
"sync_snapshot_no_data": "Keine Daten zum Sichern! Lade erstmal alles hoch!",
"server_error": "Server error!",
"dialog_confirm": "Fortfahren",
"description": "Beschreibung",
"full_screen_player": "Vollbildmodus aktivieren",
@ -311,8 +325,6 @@
"default_skip_intro_length": "Standardlänge für Intro überspringen",
"default_playback_speed_length": "Standardlänge für Wiedergabegeschwindigkeit",
"updateProgressAfterReading": "Fortschritt nach dem Lesen aktualisieren",
"syncAfterReading": "Nach dem Lesen oder Anschauen synchronisieren",
"syncOnAppLaunch": "Beim Öffnen der App synchronisieren",
"no_sources_installed": "Keine Quellen installiert!",
"show_extensions": "Erweiterungen anzeigen",
"default_skip_forward_skip_length": "Standardmäßige Länge des Vorwärtsspringens",
@ -389,8 +401,6 @@
"reorder_navigation_description": "Du kannst die Anordnung und Sichbarheit der Navigation für dich selber anpassen.",
"novel_sources": "Novel-Quellen",
"novel_extensions": "Novel-Erweiterungen",
"sync_after_reading": "Nach dem Lesen oder Ansehen synchronisieren",
"sync_on_app_launch": "Beim Starten der App synchronisieren",
"data_and_storage": "Daten und Speicher",
"download_location_info": "Wird für Kapitel-Downloads verwendet",
"storage": "Speicher",
@ -400,5 +410,11 @@
"app_settings": "App-Einstellungen",
"sources_settings": "Quellen-Einstellungen",
"include_sensitive_settings": "Sensible Einstellungen einbeziehen (z. B. Tracker-Login-Tokens)",
"create": "Erstellen"
"create": "Erstellen",
"empty_extensions_repo": "Du hast derzeit keinen Repository-Link hier. Klicke auf das Plus-Symbol, um einen hinzuzufügen!",
"add_extensions_repo": "Repository-Link hinzufügen",
"remove_extensions_repo": "Repository-Link entfernen",
"manage_manga_repo_url": "Verwalte Repository-Links für Manga",
"manage_anime_repo_url": "Verwalte Repository-Links für Anime",
"manage_novel_repo_url": "Verwalte Repository-Links für Novellen"
}

View file

@ -209,10 +209,24 @@
"sync_upload_failed": "Upload failed",
"sync_download_failed": "Download failed",
"sync_button_sync": "Sync progress",
"sync_button_snapshot": "Create snapshot",
"sync_button_upload": "Full upload",
"sync_button_download": "Full download",
"sync_confirm_snapshot": "Request the server to create a remote copy of the current backup!",
"sync_confirm_upload": "A full upload will completely replace the remote data with your current one!",
"sync_confirm_download": "A full download will completely replace your current data with the remote one!",
"sync_on": "Enable sync",
"sync_pending_manga": "Manga changes pending",
"sync_pending_category": "Category changes pending",
"sync_pending_chapter": "Chapter changes pending",
"sync_pending_history": "History changes pending",
"sync_pending_update": "Update changes pending",
"sync_pending_extension": "Extension changes pending",
"sync_pending_track": "Track changes pending",
"sync_snapshot_creating": "Creating snapshot...",
"sync_snapshot_created": "Snapshot created!",
"sync_snapshot_no_data": "No data to create a snapshot! Do a full upload first!",
"server_error": "Server error!",
"dialog_confirm": "Confirm",
"description": "Description",
"reorder_navigation": "Customize navigation",
@ -315,8 +329,6 @@
"default_skip_intro_length": "Default Skip intro length",
"default_playback_speed_length": "Default Playback speed length",
"updateProgressAfterReading": "Update progress after reading",
"sync_after_reading": "Sync after reading or watching",
"sync_on_app_launch": "Sync when opening the app",
"no_sources_installed": "No sources installed!",
"show_extensions": "Show extensions",
"default_skip_forward_skip_length": "Default skip forward skip length",
@ -399,5 +411,22 @@
"sources_settings": "Sources settings",
"include_sensitive_settings": "Include sensitive settings (e.g., tracker login tokens)",
"create": "Create",
"downloads_are_limited_to_wifi": "Downloads are limited to Wi-Fi only"
"downloads_are_limited_to_wifi": "Downloads are limited to Wi-Fi only",
"manga_extensions_repo": "Manga extensions repo",
"anime_extensions_repo": "Anime extensions repo",
"novel_extensions_repo": "Novel extensions repo",
"undefined": "undefined",
"empty_extensions_repo": "You don't have any repository urls here. Click on the plus button to add one!",
"add_extensions_repo": "Add repo URL",
"remove_extensions_repo": "Remove repo URL",
"manage_manga_repo_urls": "Manage Manga Repo URLs",
"manage_anime_repo_urls": "Manage Anime Repo URLs",
"manage_novel_repo_urls": "Manage Novel Repo URLs",
"url_cannot_be_empty": "URL cannot be empty",
"url_must_end_with_dot_json": "URL must end with .json",
"repo_url": "Repo URL",
"invalid_url_format": "Invalid URL format",
"clear_all_sources": "Clear all sources",
"clear_all_sources_msg": "This will completely erase all sources of the application. Are you sure you want to continue?",
"sources_cleared": "Sources cleared!!!"
}

View file

@ -185,6 +185,12 @@ class Settings {
int? novelGridSize;
List<Repo>? mangaExtensionsRepo;
List<Repo>? animeExtensionsRepo;
List<Repo>? novelExtensionsRepo;
@enumerated
late SectionType disableSectionType;
@ -325,7 +331,10 @@ class Settings {
this.novelTextAlign = NovelTextAlign.left,
this.navigationOrder,
this.hideItems,
this.clearChapterCacheOnAppLaunch = false});
this.clearChapterCacheOnAppLaunch = false,
this.mangaExtensionsRepo,
this.animeExtensionsRepo,
this.novelExtensionsRepo});
Settings.fromJson(Map<String, dynamic> json) {
animatePageTransitions = json['animatePageTransitions'];
@ -507,6 +516,21 @@ class Settings {
hideItems = (json['hideItems'] as List).cast<String>();
}
clearChapterCacheOnAppLaunch = json['clearChapterCacheOnAppLaunch'];
if (json['mangaExtensionsRepo'] != null) {
mangaExtensionsRepo = (json['mangaExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
}
if (json['animeExtensionsRepo'] != null) {
animeExtensionsRepo = (json['animeExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
}
if (json['novelExtensionsRepo'] != null) {
novelExtensionsRepo = (json['novelExtensionsRepo'] as List)
.map((e) => Repo.fromJson(json))
.toList();
}
}
Map<String, dynamic> toJson() => {
@ -623,7 +647,13 @@ class Settings {
'novelTextAlign': novelTextAlign.index,
'navigationOrder': navigationOrder,
'hideItems': hideItems,
'clearChapterCacheOnAppLaunch': clearChapterCacheOnAppLaunch
'clearChapterCacheOnAppLaunch': clearChapterCacheOnAppLaunch,
'mangaExtensionsRepo':
mangaExtensionsRepo?.map((e) => e.toJson()).toList(),
'animeExtensionsRepo':
animeExtensionsRepo?.map((e) => e.toJson()).toList(),
'novelExtensionsRepo':
novelExtensionsRepo?.map((e) => e.toJson()).toList()
};
}
@ -796,6 +826,24 @@ class AutoScrollPages {
{'mangaId': mangaId, 'pageOffset': pageOffset, 'autoScroll': autoScroll};
}
@embedded
class Repo {
String? name;
String? website;
String? jsonUrl;
Repo({this.name, this.website, this.jsonUrl});
Repo.fromJson(Map<String, dynamic> json) {
name = json['name'];
website = json['website'];
jsonUrl = json['jsonUrl'];
}
Map<String, dynamic> toJson() =>
{'name': name, 'website': website, 'jsonUrl': jsonUrl};
}
@embedded
class PersonalPageMode {
int? mangaId;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/m_source.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
part 'source.g.dart';
@collection
@ -64,6 +65,8 @@ class Source {
@enumerated
SourceCodeLanguage sourceCodeLanguage = SourceCodeLanguage.dart;
Repo? repo;
Source(
{this.id = 0,
this.name = '',
@ -91,7 +94,8 @@ class Source {
this.appMinVerReq = "",
this.additionalParams = "",
this.isLocal = false,
this.isObsolete = false});
this.isObsolete = false,
this.repo});
Source.fromJson(Map<String, dynamic> json) {
apiUrl = json['apiUrl'];
@ -123,6 +127,7 @@ class Source {
isLocal = json['isLocal'];
sourceCodeLanguage =
SourceCodeLanguage.values[json['sourceCodeLanguage'] ?? 0];
repo = json['repo'] != null ? Repo.fromJson(json['repo']) : null;
}
Map<String, dynamic> toJson() => {
@ -153,7 +158,8 @@ class Source {
'additionalParams': additionalParams,
'sourceCodeLanguage': sourceCodeLanguage.index,
'isObsolete': isObsolete,
'isLocal': isLocal
'isLocal': isLocal,
'repo': repo?.toJson()
};
bool get isTorrent => (typeSource?.toLowerCase() ?? "") == "torrent";

View file

@ -128,34 +128,40 @@ const SourceSchema = CollectionSchema(
name: r'name',
type: IsarType.string,
),
r'sourceCode': PropertySchema(
r'repo': PropertySchema(
id: 22,
name: r'repo',
type: IsarType.object,
target: r'Repo',
),
r'sourceCode': PropertySchema(
id: 23,
name: r'sourceCode',
type: IsarType.string,
),
r'sourceCodeLanguage': PropertySchema(
id: 23,
id: 24,
name: r'sourceCodeLanguage',
type: IsarType.byte,
enumMap: _SourcesourceCodeLanguageEnumValueMap,
),
r'sourceCodeUrl': PropertySchema(
id: 24,
id: 25,
name: r'sourceCodeUrl',
type: IsarType.string,
),
r'typeSource': PropertySchema(
id: 25,
id: 26,
name: r'typeSource',
type: IsarType.string,
),
r'version': PropertySchema(
id: 26,
id: 27,
name: r'version',
type: IsarType.string,
),
r'versionLast': PropertySchema(
id: 27,
id: 28,
name: r'versionLast',
type: IsarType.string,
)
@ -167,7 +173,7 @@ const SourceSchema = CollectionSchema(
idName: r'id',
indexes: {},
links: {},
embeddedSchemas: {},
embeddedSchemas: {r'Repo': RepoSchema},
getId: _sourceGetId,
getLinks: _sourceGetLinks,
attach: _sourceAttach,
@ -240,6 +246,13 @@ int _sourceEstimateSize(
bytesCount += 3 + value.length * 3;
}
}
{
final value = object.repo;
if (value != null) {
bytesCount +=
3 + RepoSchema.estimateSize(value, allOffsets[Repo]!, allOffsets);
}
}
{
final value = object.sourceCode;
if (value != null) {
@ -301,12 +314,18 @@ void _sourceSerialize(
writer.writeString(offsets[19], object.lang);
writer.writeBool(offsets[20], object.lastUsed);
writer.writeString(offsets[21], object.name);
writer.writeString(offsets[22], object.sourceCode);
writer.writeByte(offsets[23], object.sourceCodeLanguage.index);
writer.writeString(offsets[24], object.sourceCodeUrl);
writer.writeString(offsets[25], object.typeSource);
writer.writeString(offsets[26], object.version);
writer.writeString(offsets[27], object.versionLast);
writer.writeObject<Repo>(
offsets[22],
allOffsets,
RepoSchema.serialize,
object.repo,
);
writer.writeString(offsets[23], object.sourceCode);
writer.writeByte(offsets[24], object.sourceCodeLanguage.index);
writer.writeString(offsets[25], object.sourceCodeUrl);
writer.writeString(offsets[26], object.typeSource);
writer.writeString(offsets[27], object.version);
writer.writeString(offsets[28], object.versionLast);
}
Source _sourceDeserialize(
@ -339,14 +358,19 @@ Source _sourceDeserialize(
lang: reader.readStringOrNull(offsets[19]),
lastUsed: reader.readBoolOrNull(offsets[20]),
name: reader.readStringOrNull(offsets[21]),
sourceCode: reader.readStringOrNull(offsets[22]),
sourceCodeUrl: reader.readStringOrNull(offsets[24]),
typeSource: reader.readStringOrNull(offsets[25]),
version: reader.readStringOrNull(offsets[26]),
versionLast: reader.readStringOrNull(offsets[27]),
repo: reader.readObjectOrNull<Repo>(
offsets[22],
RepoSchema.deserialize,
allOffsets,
),
sourceCode: reader.readStringOrNull(offsets[23]),
sourceCodeUrl: reader.readStringOrNull(offsets[25]),
typeSource: reader.readStringOrNull(offsets[26]),
version: reader.readStringOrNull(offsets[27]),
versionLast: reader.readStringOrNull(offsets[28]),
);
object.sourceCodeLanguage = _SourcesourceCodeLanguageValueEnumMap[
reader.readByteOrNull(offsets[23])] ??
reader.readByteOrNull(offsets[24])] ??
SourceCodeLanguage.dart;
return object;
}
@ -404,19 +428,25 @@ P _sourceDeserializeProp<P>(
case 21:
return (reader.readStringOrNull(offset)) as P;
case 22:
return (reader.readStringOrNull(offset)) as P;
return (reader.readObjectOrNull<Repo>(
offset,
RepoSchema.deserialize,
allOffsets,
)) as P;
case 23:
return (reader.readStringOrNull(offset)) as P;
case 24:
return (_SourcesourceCodeLanguageValueEnumMap[
reader.readByteOrNull(offset)] ??
SourceCodeLanguage.dart) as P;
case 24:
return (reader.readStringOrNull(offset)) as P;
case 25:
return (reader.readStringOrNull(offset)) as P;
case 26:
return (reader.readStringOrNull(offset)) as P;
case 27:
return (reader.readStringOrNull(offset)) as P;
case 28:
return (reader.readStringOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -2388,6 +2418,22 @@ extension SourceQueryFilter on QueryBuilder<Source, Source, QFilterCondition> {
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> repoIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'repo',
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> repoIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'repo',
));
});
}
QueryBuilder<Source, Source, QAfterFilterCondition> sourceCodeIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
@ -3175,7 +3221,14 @@ extension SourceQueryFilter on QueryBuilder<Source, Source, QFilterCondition> {
}
}
extension SourceQueryObject on QueryBuilder<Source, Source, QFilterCondition> {}
extension SourceQueryObject on QueryBuilder<Source, Source, QFilterCondition> {
QueryBuilder<Source, Source, QAfterFilterCondition> repo(
FilterQuery<Repo> q) {
return QueryBuilder.apply(this, (query) {
return query.object(q, r'repo');
});
}
}
extension SourceQueryLinks on QueryBuilder<Source, Source, QFilterCondition> {}
@ -4194,6 +4247,12 @@ extension SourceQueryProperty on QueryBuilder<Source, Source, QQueryProperty> {
});
}
QueryBuilder<Source, Repo?, QQueryOperations> repoProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'repo');
});
}
QueryBuilder<Source, String?, QQueryOperations> sourceCodeProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'sourceCode');

View file

@ -14,6 +14,7 @@ import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/cached_network.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/utils/language.dart';
import 'package:url_launcher/url_launcher.dart';
class ExtensionDetail extends ConsumerStatefulWidget {
final Source source;
@ -24,19 +25,36 @@ class ExtensionDetail extends ConsumerStatefulWidget {
}
class _ExtensionDetailState extends ConsumerState<ExtensionDetail> {
late Source source = widget.source;
late Source source = isar.sources.getSync(widget.source.id!)!;
late List<SourcePreference> sourcePreference =
getSourcePreference(source: source)
.map((e) => getSourcePreferenceEntry(e.key!, source.id!))
.toList();
Future<void> _launchInBrowser(Uri url) async {
if (!await launchUrl(
url,
mode: LaunchMode.externalApplication,
)) {
throw 'Could not launch $url';
}
}
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.extension_detail),
leading: BackButton(onPressed: () => Navigator.pop(context, source))),
title: Text(l10n.extension_detail),
leading: BackButton(onPressed: () => Navigator.pop(context, source)),
actions: [
if (source.repo?.website != null)
IconButton(
onPressed: () {
_launchInBrowser(Uri.parse(source.repo!.website!));
},
icon: Icon(Icons.open_in_new_outlined))
],
),
body: SingleChildScrollView(
child: Column(
children: [

View file

@ -6,7 +6,7 @@ part of 'download_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$downloadChapterHash() => r'ef2f5195c42fa8e17fc3d8cd5ffecc67f82472e9';
String _$downloadChapterHash() => r'08a7196ae7da5d980629ef80d04ab9b251006eaf';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,5 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
@ -36,6 +41,39 @@ class BrowseSScreen extends ConsumerWidget {
],
),
),
ListTile(
onTap: () {
context.push("/SourceRepositories",
extra: ItemType.manga);
},
title: Text(l10n.manga_extensions_repo),
subtitle: Text(
l10n.manage_manga_repo_urls,
style: TextStyle(
fontSize: 11, color: context.secondaryColor),
)),
ListTile(
onTap: () {
context.push("/SourceRepositories",
extra: ItemType.anime);
},
title: Text(l10n.anime_extensions_repo),
subtitle: Text(
l10n.manage_anime_repo_urls,
style: TextStyle(
fontSize: 11, color: context.secondaryColor),
)),
ListTile(
onTap: () {
context.push("/SourceRepositories",
extra: ItemType.novel);
},
title: Text(l10n.novel_extensions_repo),
subtitle: Text(
l10n.manage_novel_repo_urls,
style: TextStyle(
fontSize: 11, color: context.secondaryColor),
)),
SwitchListTile(
value: checkForExtensionUpdates,
title: Text(l10n.check_for_extension_updates),
@ -45,6 +83,28 @@ class BrowseSScreen extends ConsumerWidget {
checkForExtensionsUpdateStateProvider.notifier)
.set(value);
}),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
child: SizedBox(
width: context.width(1),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () =>
_showClearAllSourcesDialog(context, l10n),
child: Text(
l10n.clear_all_sources,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
),
),
if (checkForExtensionUpdates)
SwitchListTile(
value: autoUpdateExtensions,
@ -91,3 +151,41 @@ class BrowseSScreen extends ConsumerWidget {
);
}
}
void _showClearAllSourcesDialog(BuildContext context, dynamic l10n) {
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
title: Text(
l10n.clear_all_sources,
),
content: Text(l10n.clear_all_sources_msg),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(ctx);
},
child: Text(l10n.cancel)),
const SizedBox(
width: 15,
),
TextButton(
onPressed: () {
isar.writeTxnSync(() {
isar.sources.clearSync();
});
Navigator.pop(ctx);
botToast(l10n.sources_cleared);
},
child: Text(l10n.ok)),
],
)
],
);
});
}

View file

@ -1,5 +1,12 @@
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/services/fetch_anime_sources.dart';
import 'package:mangayomi/services/fetch_manga_sources.dart';
import 'package:mangayomi/services/fetch_novel_sources.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'browse_state_provider.g.dart';
@ -18,6 +25,46 @@ class OnlyIncludePinnedSourceState extends _$OnlyIncludePinnedSourceState {
}
}
@riverpod
class ExtensionsRepoState extends _$ExtensionsRepoState {
@override
List<Repo> build(ItemType itemType) {
final settings = isar.settings.getSync(227)!;
return switch (itemType) {
ItemType.manga => settings.mangaExtensionsRepo,
ItemType.anime => settings.animeExtensionsRepo,
_ => settings.novelExtensionsRepo,
} ??
[];
}
void set(List<Repo> value) {
final settings = isar.settings.getSync(227)!;
state = value;
isar.writeTxnSync(() {
final a = switch (itemType) {
ItemType.manga =>
isar.settings.putSync(settings..mangaExtensionsRepo = value),
ItemType.anime =>
isar.settings.putSync(settings..animeExtensionsRepo = value),
_ => isar.settings.putSync(settings..novelExtensionsRepo = value),
};
a;
});
try {
final a = switch (itemType) {
ItemType.manga => ref.refresh(
fetchMangaSourcesListProvider(id: null, reFresh: false).future),
ItemType.anime => ref.refresh(
fetchAnimeSourcesListProvider(id: null, reFresh: false).future),
_ => ref.refresh(
fetchNovelSourcesListProvider(id: null, reFresh: false).future),
};
Future.wait([a]);
} catch (_) {}
}
}
@riverpod
class AutoUpdateExtensionsState extends _$AutoUpdateExtensionsState {
@override
@ -47,3 +94,22 @@ class CheckForExtensionsUpdateState extends _$CheckForExtensionsUpdateState {
isar.settings.putSync(settings!..checkForExtensionUpdates = value));
}
}
@riverpod
Future<Repo> getRepoInfos(Ref ref, {required String jsonUrl}) async {
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
Map<String, dynamic> infos = {};
final match = RegExp(r'^(.*)/[^/]+\.json$').firstMatch(jsonUrl);
if (match != null) {
String url = match.group(1)!;
final req = await http.get(Uri.parse("$url/repo.json"));
if (req.statusCode == 200) {
infos.addAll(jsonDecode(req.body));
}
}
infos["jsonUrl"] = jsonUrl;
return Repo.fromJson(infos);
}

View file

@ -6,6 +6,157 @@ part of 'browse_state_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$getRepoInfosHash() => r'd5d5eca9fd23accd515bf51b470edb99a5d58733';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
/// See also [getRepoInfos].
@ProviderFor(getRepoInfos)
const getRepoInfosProvider = GetRepoInfosFamily();
/// See also [getRepoInfos].
class GetRepoInfosFamily extends Family<AsyncValue<Repo>> {
/// See also [getRepoInfos].
const GetRepoInfosFamily();
/// See also [getRepoInfos].
GetRepoInfosProvider call({
required String jsonUrl,
}) {
return GetRepoInfosProvider(
jsonUrl: jsonUrl,
);
}
@override
GetRepoInfosProvider getProviderOverride(
covariant GetRepoInfosProvider provider,
) {
return call(
jsonUrl: provider.jsonUrl,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'getRepoInfosProvider';
}
/// See also [getRepoInfos].
class GetRepoInfosProvider extends AutoDisposeFutureProvider<Repo> {
/// See also [getRepoInfos].
GetRepoInfosProvider({
required String jsonUrl,
}) : this._internal(
(ref) => getRepoInfos(
ref as GetRepoInfosRef,
jsonUrl: jsonUrl,
),
from: getRepoInfosProvider,
name: r'getRepoInfosProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$getRepoInfosHash,
dependencies: GetRepoInfosFamily._dependencies,
allTransitiveDependencies:
GetRepoInfosFamily._allTransitiveDependencies,
jsonUrl: jsonUrl,
);
GetRepoInfosProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.jsonUrl,
}) : super.internal();
final String jsonUrl;
@override
Override overrideWith(
FutureOr<Repo> Function(GetRepoInfosRef provider) create,
) {
return ProviderOverride(
origin: this,
override: GetRepoInfosProvider._internal(
(ref) => create(ref as GetRepoInfosRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
jsonUrl: jsonUrl,
),
);
}
@override
AutoDisposeFutureProviderElement<Repo> createElement() {
return _GetRepoInfosProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is GetRepoInfosProvider && other.jsonUrl == jsonUrl;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, jsonUrl.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin GetRepoInfosRef on AutoDisposeFutureProviderRef<Repo> {
/// The parameter `jsonUrl` of this provider.
String get jsonUrl;
}
class _GetRepoInfosProviderElement
extends AutoDisposeFutureProviderElement<Repo> with GetRepoInfosRef {
_GetRepoInfosProviderElement(super.provider);
@override
String get jsonUrl => (origin as GetRepoInfosProvider).jsonUrl;
}
String _$onlyIncludePinnedSourceStateHash() =>
r'77c4bff96e186c4bce0a5c312871ceec88a269d0';
@ -23,6 +174,153 @@ final onlyIncludePinnedSourceStateProvider =
);
typedef _$OnlyIncludePinnedSourceState = AutoDisposeNotifier<bool>;
String _$extensionsRepoStateHash() =>
r'9e59b257433ed7f999dd4800f6ecb8c13c8b2c6a';
abstract class _$ExtensionsRepoState
extends BuildlessAutoDisposeNotifier<List<Repo>> {
late final ItemType itemType;
List<Repo> build(
ItemType itemType,
);
}
/// See also [ExtensionsRepoState].
@ProviderFor(ExtensionsRepoState)
const extensionsRepoStateProvider = ExtensionsRepoStateFamily();
/// See also [ExtensionsRepoState].
class ExtensionsRepoStateFamily extends Family<List<Repo>> {
/// See also [ExtensionsRepoState].
const ExtensionsRepoStateFamily();
/// See also [ExtensionsRepoState].
ExtensionsRepoStateProvider call(
ItemType itemType,
) {
return ExtensionsRepoStateProvider(
itemType,
);
}
@override
ExtensionsRepoStateProvider getProviderOverride(
covariant ExtensionsRepoStateProvider provider,
) {
return call(
provider.itemType,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'extensionsRepoStateProvider';
}
/// See also [ExtensionsRepoState].
class ExtensionsRepoStateProvider
extends AutoDisposeNotifierProviderImpl<ExtensionsRepoState, List<Repo>> {
/// See also [ExtensionsRepoState].
ExtensionsRepoStateProvider(
ItemType itemType,
) : this._internal(
() => ExtensionsRepoState()..itemType = itemType,
from: extensionsRepoStateProvider,
name: r'extensionsRepoStateProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$extensionsRepoStateHash,
dependencies: ExtensionsRepoStateFamily._dependencies,
allTransitiveDependencies:
ExtensionsRepoStateFamily._allTransitiveDependencies,
itemType: itemType,
);
ExtensionsRepoStateProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.itemType,
}) : super.internal();
final ItemType itemType;
@override
List<Repo> runNotifierBuild(
covariant ExtensionsRepoState notifier,
) {
return notifier.build(
itemType,
);
}
@override
Override overrideWith(ExtensionsRepoState Function() create) {
return ProviderOverride(
origin: this,
override: ExtensionsRepoStateProvider._internal(
() => create()..itemType = itemType,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
itemType: itemType,
),
);
}
@override
AutoDisposeNotifierProviderElement<ExtensionsRepoState, List<Repo>>
createElement() {
return _ExtensionsRepoStateProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ExtensionsRepoStateProvider && other.itemType == itemType;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, itemType.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin ExtensionsRepoStateRef on AutoDisposeNotifierProviderRef<List<Repo>> {
/// The parameter `itemType` of this provider.
ItemType get itemType;
}
class _ExtensionsRepoStateProviderElement
extends AutoDisposeNotifierProviderElement<ExtensionsRepoState, List<Repo>>
with ExtensionsRepoStateRef {
_ExtensionsRepoStateProviderElement(super.provider);
@override
ItemType get itemType => (origin as ExtensionsRepoStateProvider).itemType;
}
String _$autoUpdateExtensionsStateHash() =>
r'30ce3c558504e005f9c85e2fc969ab7a581510cd';

View file

@ -0,0 +1,308 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/eval/model/m_bridge.dart';
import 'package:mangayomi/models/manga.dart';
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';
class SourceRepositories extends ConsumerStatefulWidget {
final ItemType itemType;
const SourceRepositories({required this.itemType, super.key});
@override
ConsumerState<SourceRepositories> createState() => _SourceRepositoriesState();
}
class _SourceRepositoriesState extends ConsumerState<SourceRepositories> {
List<Repo> _entries = [];
String urlInput = "";
@override
Widget build(BuildContext context) {
final l10n = l10nLocalizations(context)!;
final repositories =
ref.watch(extensionsRepoStateProvider(widget.itemType));
final data = AsyncValue.data(repositories);
return Scaffold(
appBar: AppBar(
title: switch (widget.itemType) {
ItemType.manga => Text(l10n.manage_manga_repo_urls),
ItemType.anime => Text(l10n.manage_anime_repo_urls),
_ => Text(l10n.manage_novel_repo_urls)
},
),
body: data.when(
data: (data) {
if (data.isEmpty) {
_entries = [];
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
l10n.empty_extensions_repo,
textAlign: TextAlign.center,
),
),
);
}
_entries = data;
return ListView.builder(
itemCount: _entries.length,
itemBuilder: (context, index) {
final repo = _entries[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Card(
child: Column(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
elevation: 0,
shadowColor: Colors.transparent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(0),
bottomRight: Radius.circular(0),
topRight: Radius.circular(10),
topLeft: Radius.circular(10)))),
onPressed: () {},
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Icon(Icons.label_outline_rounded),
const SizedBox(
width: 10,
),
Expanded(child: Text(repo.name ?? repo.jsonUrl!))
],
)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
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,
)),
],
)
],
);
},
);
});
},
icon: const Icon(Icons.delete_outlined))
],
),
],
)
],
),
),
);
},
);
},
error: (Object error, StackTrace stackTrace) {
_entries = [];
return Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
l10n.empty_extensions_repo,
textAlign: TextAlign.center,
),
),
);
},
loading: () {
return const ProgressCenter();
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
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,
),
TextButton(
onPressed: controller.text.isEmpty ||
!controller.text.endsWith(".json")
? null
: () async {
try {
final mangaRepos = ref
.read(
extensionsRepoStateProvider(
widget.itemType))
.toList();
final repo = await ref.read(
getRepoInfosProvider(
jsonUrl:
controller.text)
.future);
mangaRepos.add(repo);
ref
.read(
extensionsRepoStateProvider(
widget.itemType)
.notifier)
.set(mangaRepos);
ref.invalidate(
extensionsRepoStateProvider(
widget.itemType));
} catch (e) {
botToast(e.toString());
}
if (context.mounted) {
Navigator.pop(context);
}
},
child: Text(
l10n.add,
style: TextStyle(
color: controller.text.isEmpty ||
!controller.text
.endsWith(".json")
? Theme.of(context)
.primaryColor
.withValues(alpha: 0.2)
: null),
)),
],
)
],
);
},
),
);
});
},
label: Row(
children: [
const Icon(Icons.add),
const SizedBox(
width: 10,
),
Text(l10n.add)
],
)),
);
}
}

View file

@ -13,6 +13,8 @@ import 'package:mangayomi/modules/browse/sources/sources_filter_screen.dart';
import 'package:mangayomi/modules/more/data_and_storage/create_backup.dart';
import 'package:mangayomi/modules/more/data_and_storage/data_and_storage.dart';
import 'package:mangayomi/modules/more/settings/appearance/custom_navigation_settings.dart';
import 'package:mangayomi/modules/more/settings/browse/source_repositories.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/novel/novel_reader_view.dart';
import 'package:mangayomi/modules/updates/updates_screen.dart';
import 'package:mangayomi/modules/more/categories/categories_screen.dart';
@ -49,10 +51,11 @@ final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@riverpod
GoRouter router(Ref ref) {
final router = RouterNotifier();
final initLocation = ref.watch(navigationOrderStateProvider).first;
return GoRouter(
observers: [BotToastNavigatorObserver()],
initialLocation: '/MangaLibrary',
initialLocation: initLocation,
debugLogDiagnostics: kDebugMode,
refreshListenable: router,
routes: router._routes,
@ -487,6 +490,25 @@ class RouterNotifier extends ChangeNotifier {
);
},
),
GoRoute(
path: "/SourceRepositories",
name: "SourceRepositories",
builder: (context, state) {
final itemType = state.extra as ItemType;
return SourceRepositories(
itemType: itemType,
);
},
pageBuilder: (context, state) {
final itemType = state.extra as ItemType;
return transitionPage(
key: state.pageKey,
child: SourceRepositories(
itemType: itemType,
),
);
},
),
GoRoute(
path: "/downloads",
name: "downloads",

View file

@ -6,7 +6,7 @@ part of 'router.dart';
// RiverpodGenerator
// **************************************************************************
String _$routerHash() => r'898ce90fa3d611eeb9ada09b0b29672c5accb22a';
String _$routerHash() => r'7a4e8651b0f12561ff4be5ca85a0142ab64a3da3';
/// See also [router].
@ProviderFor(router)

View file

@ -1,4 +1,5 @@
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/services/fetch_sources_list.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -6,14 +7,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'fetch_anime_sources.g.dart';
@Riverpod(keepAlive: true)
Future fetchAnimeSourcesList(Ref ref, {int? id, required bool reFresh}) async {
Future<void> fetchAnimeSourcesList(Ref ref,
{int? id, required bool reFresh}) async {
if (ref.watch(checkForExtensionsUpdateStateProvider) || reFresh) {
await fetchSourcesList(
sourcesIndexUrl:
"",
refresh: reFresh,
id: id,
ref: ref,
itemType: ItemType.anime);
final repos = ref.watch(extensionsRepoStateProvider(ItemType.anime));
for (Repo repo in repos) {
await fetchSourcesList(
repo: repo,
refresh: reFresh,
id: id,
ref: ref,
itemType: ItemType.anime);
}
}
}

View file

@ -7,7 +7,7 @@ part of 'fetch_anime_sources.dart';
// **************************************************************************
String _$fetchAnimeSourcesListHash() =>
r'34db8fac67fb2d445645e62adc68b6d13d481897';
r'e7f673d37239c74f3403de3a234bbc1d6e171332';
/// Copied from Dart SDK
class _SystemHash {
@ -35,7 +35,7 @@ class _SystemHash {
const fetchAnimeSourcesListProvider = FetchAnimeSourcesListFamily();
/// See also [fetchAnimeSourcesList].
class FetchAnimeSourcesListFamily extends Family<AsyncValue> {
class FetchAnimeSourcesListFamily extends Family<AsyncValue<void>> {
/// See also [fetchAnimeSourcesList].
const FetchAnimeSourcesListFamily();
@ -76,7 +76,7 @@ class FetchAnimeSourcesListFamily extends Family<AsyncValue> {
}
/// See also [fetchAnimeSourcesList].
class FetchAnimeSourcesListProvider extends FutureProvider<Object?> {
class FetchAnimeSourcesListProvider extends FutureProvider<void> {
/// See also [fetchAnimeSourcesList].
FetchAnimeSourcesListProvider({
int? id,
@ -116,7 +116,7 @@ class FetchAnimeSourcesListProvider extends FutureProvider<Object?> {
@override
Override overrideWith(
FutureOr<Object?> Function(FetchAnimeSourcesListRef provider) create,
FutureOr<void> Function(FetchAnimeSourcesListRef provider) create,
) {
return ProviderOverride(
origin: this,
@ -134,7 +134,7 @@ class FetchAnimeSourcesListProvider extends FutureProvider<Object?> {
}
@override
FutureProviderElement<Object?> createElement() {
FutureProviderElement<void> createElement() {
return _FetchAnimeSourcesListProviderElement(this);
}
@ -157,7 +157,7 @@ class FetchAnimeSourcesListProvider extends FutureProvider<Object?> {
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin FetchAnimeSourcesListRef on FutureProviderRef<Object?> {
mixin FetchAnimeSourcesListRef on FutureProviderRef<void> {
/// The parameter `id` of this provider.
int? get id;
@ -165,8 +165,8 @@ mixin FetchAnimeSourcesListRef on FutureProviderRef<Object?> {
bool get reFresh;
}
class _FetchAnimeSourcesListProviderElement
extends FutureProviderElement<Object?> with FetchAnimeSourcesListRef {
class _FetchAnimeSourcesListProviderElement extends FutureProviderElement<void>
with FetchAnimeSourcesListRef {
_FetchAnimeSourcesListProviderElement(super.provider);
@override

View file

@ -1,4 +1,5 @@
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/services/fetch_sources_list.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -6,14 +7,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'fetch_manga_sources.g.dart';
@Riverpod(keepAlive: true)
Future fetchMangaSourcesList(Ref ref, {int? id, required reFresh}) async {
Future<void> fetchMangaSourcesList(Ref ref, {int? id, required reFresh}) async {
if (ref.watch(checkForExtensionsUpdateStateProvider) || reFresh) {
await fetchSourcesList(
sourcesIndexUrl:
"",
refresh: reFresh,
id: id,
ref: ref,
itemType: ItemType.manga);
final repos = ref.watch(extensionsRepoStateProvider(ItemType.manga));
for (Repo repo in repos) {
await fetchSourcesList(
repo: repo,
refresh: reFresh,
id: id,
ref: ref,
itemType: ItemType.manga);
}
}
}

View file

@ -7,7 +7,7 @@ part of 'fetch_manga_sources.dart';
// **************************************************************************
String _$fetchMangaSourcesListHash() =>
r'b56d2a229f2d0a2ef4dc93d9f06cc8485dcd2285';
r'176206caf5c51a94b100866d5cdb612d2a5c2fb7';
/// Copied from Dart SDK
class _SystemHash {
@ -35,7 +35,7 @@ class _SystemHash {
const fetchMangaSourcesListProvider = FetchMangaSourcesListFamily();
/// See also [fetchMangaSourcesList].
class FetchMangaSourcesListFamily extends Family<AsyncValue> {
class FetchMangaSourcesListFamily extends Family<AsyncValue<void>> {
/// See also [fetchMangaSourcesList].
const FetchMangaSourcesListFamily();
@ -76,7 +76,7 @@ class FetchMangaSourcesListFamily extends Family<AsyncValue> {
}
/// See also [fetchMangaSourcesList].
class FetchMangaSourcesListProvider extends FutureProvider<Object?> {
class FetchMangaSourcesListProvider extends FutureProvider<void> {
/// See also [fetchMangaSourcesList].
FetchMangaSourcesListProvider({
int? id,
@ -116,7 +116,7 @@ class FetchMangaSourcesListProvider extends FutureProvider<Object?> {
@override
Override overrideWith(
FutureOr<Object?> Function(FetchMangaSourcesListRef provider) create,
FutureOr<void> Function(FetchMangaSourcesListRef provider) create,
) {
return ProviderOverride(
origin: this,
@ -134,7 +134,7 @@ class FetchMangaSourcesListProvider extends FutureProvider<Object?> {
}
@override
FutureProviderElement<Object?> createElement() {
FutureProviderElement<void> createElement() {
return _FetchMangaSourcesListProviderElement(this);
}
@ -157,7 +157,7 @@ class FetchMangaSourcesListProvider extends FutureProvider<Object?> {
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin FetchMangaSourcesListRef on FutureProviderRef<Object?> {
mixin FetchMangaSourcesListRef on FutureProviderRef<void> {
/// The parameter `id` of this provider.
int? get id;
@ -165,8 +165,8 @@ mixin FetchMangaSourcesListRef on FutureProviderRef<Object?> {
dynamic get reFresh;
}
class _FetchMangaSourcesListProviderElement
extends FutureProviderElement<Object?> with FetchMangaSourcesListRef {
class _FetchMangaSourcesListProviderElement extends FutureProviderElement<void>
with FetchMangaSourcesListRef {
_FetchMangaSourcesListProviderElement(super.provider);
@override

View file

@ -1,4 +1,5 @@
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/services/fetch_sources_list.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -6,14 +7,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
part 'fetch_novel_sources.g.dart';
@Riverpod(keepAlive: true)
Future fetchNovelSourcesList(Ref ref, {int? id, required reFresh}) async {
Future<void> fetchNovelSourcesList(Ref ref, {int? id, required reFresh}) async {
if (ref.watch(checkForExtensionsUpdateStateProvider) || reFresh) {
await fetchSourcesList(
sourcesIndexUrl:
"",
refresh: reFresh,
id: id,
ref: ref,
itemType: ItemType.novel);
final repos = ref.watch(extensionsRepoStateProvider(ItemType.novel));
for (Repo repo in repos) {
await fetchSourcesList(
repo: repo,
refresh: reFresh,
id: id,
ref: ref,
itemType: ItemType.novel);
}
}
}

View file

@ -7,7 +7,7 @@ part of 'fetch_novel_sources.dart';
// **************************************************************************
String _$fetchNovelSourcesListHash() =>
r'9a7afa9c301d2f4be51c074e5a81117f6d01352a';
r'882ee56332290a6fe71d38a8378de847e4386e3a';
/// Copied from Dart SDK
class _SystemHash {
@ -35,7 +35,7 @@ class _SystemHash {
const fetchNovelSourcesListProvider = FetchNovelSourcesListFamily();
/// See also [fetchNovelSourcesList].
class FetchNovelSourcesListFamily extends Family<AsyncValue> {
class FetchNovelSourcesListFamily extends Family<AsyncValue<void>> {
/// See also [fetchNovelSourcesList].
const FetchNovelSourcesListFamily();
@ -76,7 +76,7 @@ class FetchNovelSourcesListFamily extends Family<AsyncValue> {
}
/// See also [fetchNovelSourcesList].
class FetchNovelSourcesListProvider extends FutureProvider<Object?> {
class FetchNovelSourcesListProvider extends FutureProvider<void> {
/// See also [fetchNovelSourcesList].
FetchNovelSourcesListProvider({
int? id,
@ -116,7 +116,7 @@ class FetchNovelSourcesListProvider extends FutureProvider<Object?> {
@override
Override overrideWith(
FutureOr<Object?> Function(FetchNovelSourcesListRef provider) create,
FutureOr<void> Function(FetchNovelSourcesListRef provider) create,
) {
return ProviderOverride(
origin: this,
@ -134,7 +134,7 @@ class FetchNovelSourcesListProvider extends FutureProvider<Object?> {
}
@override
FutureProviderElement<Object?> createElement() {
FutureProviderElement<void> createElement() {
return _FetchNovelSourcesListProviderElement(this);
}
@ -157,7 +157,7 @@ class FetchNovelSourcesListProvider extends FutureProvider<Object?> {
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin FetchNovelSourcesListRef on FutureProviderRef<Object?> {
mixin FetchNovelSourcesListRef on FutureProviderRef<void> {
/// The parameter `id` of this provider.
int? get id;
@ -165,8 +165,8 @@ mixin FetchNovelSourcesListRef on FutureProviderRef<Object?> {
dynamic get reFresh;
}
class _FetchNovelSourcesListProviderElement
extends FutureProviderElement<Object?> with FetchNovelSourcesListRef {
class _FetchNovelSourcesListProviderElement extends FutureProviderElement<void>
with FetchNovelSourcesListRef {
_FetchNovelSourcesListProviderElement(super.provider);
@override

View file

@ -4,6 +4,7 @@ import 'package:isar/isar.dart';
import 'package:mangayomi/eval/lib.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:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:mangayomi/services/http/m_client.dart';
@ -12,11 +13,14 @@ import 'package:package_info_plus/package_info_plus.dart';
Future<void> fetchSourcesList(
{int? id,
required bool refresh,
required String sourcesIndexUrl,
required Ref ref,
required ItemType itemType}) async {
required ItemType itemType,
required Repo? repo}) async {
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
final req = await http.get(Uri.parse(sourcesIndexUrl));
final url = repo?.jsonUrl;
if (url == null) return;
final req = await http.get(Uri.parse(url));
final sourceList =
(jsonDecode(req.body) as List).map((e) => Source.fromJson(e)).toList();
@ -35,30 +39,33 @@ Future<void> fetchSourcesList(
getExtensionService(source..sourceCode = req.body)
.getHeaders();
isar.writeTxnSync(() {
isar.sources.putSync(sourc
..headers = jsonEncode(headers)
..isAdded = true
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..sourceCodeLanguage = source.sourceCodeLanguage
..additionalParams = source.additionalParams ?? ""
..isObsolete = false);
isar.sources.putSync(
sourc
..headers = jsonEncode(headers)
..isAdded = true
..sourceCode = req.body
..sourceCodeUrl = source.sourceCodeUrl
..id = id
..apiUrl = source.apiUrl
..baseUrl = source.baseUrl
..dateFormat = source.dateFormat
..dateFormatLocale = source.dateFormatLocale
..hasCloudflare = source.hasCloudflare
..iconUrl = source.iconUrl
..typeSource = source.typeSource
..lang = source.lang
..isNsfw = source.isNsfw
..name = source.name
..version = source.version
..versionLast = source.version
..itemType = itemType
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..sourceCodeLanguage = source.sourceCodeLanguage
..additionalParams = source.additionalParams ?? ""
..isObsolete = false
..repo = repo,
);
});
// log("successfully installed or updated");
}
@ -98,7 +105,8 @@ Future<void> fetchSourcesList(
..appMinVerReq = source.appMinVerReq
..sourceCodeLanguage = source.sourceCodeLanguage
..additionalParams = source.additionalParams ?? ""
..isObsolete = false);
..isObsolete = false
..repo = repo);
});
} else {
// log("update aivalable");
@ -127,7 +135,8 @@ Future<void> fetchSourcesList(
..sourceCodeLanguage = source.sourceCodeLanguage
..isFullData = source.isFullData ?? false
..appMinVerReq = source.appMinVerReq
..isObsolete = false);
..isObsolete = false
..repo = repo);
// log("new source");
}
}
@ -153,21 +162,6 @@ void checkIfSourceIsObsolete(List<Source> sourceList, ItemType itemType) {
}
}
}
removeNsfwObsoleteSources();
}
void removeNsfwObsoleteSources() {
final ids = isar.sources
.filter()
.idIsNotNull()
.isNsfwEqualTo(true)
.isObsoleteEqualTo(true)
.findAllSync()
.map((e) => e.id!)
.toList();
isar.writeTxnSync(() {
isar.sources.deleteAllSync(ids);
});
}
int compareVersions(String version1, String version2) {