Merge remote-tracking branch 'upstream/main' into Correct-directory

This commit is contained in:
NBA2K1 2025-10-09 19:13:22 +02:00
commit 462e9b5fe7
64 changed files with 1739 additions and 478 deletions

View file

@ -107,6 +107,15 @@ jobs:
- name: Install the CLI tool
run: cargo install 'flutter_rust_bridge_codegen'
- name: Select Xcode 16.2
run: sudo xcode-select -switch /Applications/Xcode_16.2.app/Contents/Developer
- name: Print Xcode version
run: xcodebuild -version
- name: Download iOS Platform
run: xcodebuild -downloadPlatform iOS
- name: flutter pub get
run: flutter pub get

View file

@ -58,6 +58,12 @@ android {
versionName = flutter.versionName
}
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']

View file

@ -546,5 +546,11 @@
"watch_order": "Watch order",
"sequels": "Sequels",
"recommendations": "Recommendations",
"recommendations_similarity": "Similarity:"
"recommendations_similarity": "Similarity:",
"local_folder_structure": "Structure of a local folder",
"local_folder": "Local folders",
"add_local_folder": "Add local folder",
"rescan_local_folder": "Rescan all local folders now",
"export_metadata": "Export metadata",
"exported": "Exported"
}

View file

@ -3350,6 +3350,42 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Similarity:'**
String get recommendations_similarity;
/// No description provided for @local_folder_structure.
///
/// In en, this message translates to:
/// **'Structure of a local folder'**
String get local_folder_structure;
/// No description provided for @local_folder.
///
/// In en, this message translates to:
/// **'Local folders'**
String get local_folder;
/// No description provided for @add_local_folder.
///
/// In en, this message translates to:
/// **'Add local folder'**
String get add_local_folder;
/// No description provided for @rescan_local_folder.
///
/// In en, this message translates to:
/// **'Rescan all local folders now'**
String get rescan_local_folder;
/// No description provided for @export_metadata.
///
/// In en, this message translates to:
/// **'Export metadata'**
String get export_metadata;
/// No description provided for @exported.
///
/// In en, this message translates to:
/// **'Exported'**
String get exported;
}
class _AppLocalizationsDelegate

View file

@ -1733,4 +1733,22 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1735,4 +1735,22 @@ class AppLocalizationsAs extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1746,4 +1746,22 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1734,4 +1734,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1751,6 +1751,24 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}
/// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`).

View file

@ -1752,4 +1752,22 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1736,4 +1736,22 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1740,4 +1740,22 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1749,4 +1749,22 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1748,6 +1748,24 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}
/// The translations for Portuguese, as used in Brazil (`pt_BR`).

View file

@ -1750,4 +1750,22 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1734,4 +1734,22 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1740,4 +1740,22 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -1705,4 +1705,22 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get recommendations_similarity => 'Similarity:';
@override
String get local_folder_structure => 'Structure of a local folder';
@override
String get local_folder => 'Local folders';
@override
String get add_local_folder => 'Add local folder';
@override
String get rescan_local_folder => 'Rescan all local folders now';
@override
String get export_metadata => 'Export metadata';
@override
String get exported => 'Exported';
}

View file

@ -286,6 +286,8 @@ class Settings {
late AlgorithmWeights? algorithmWeights;
List<String>? localFolders;
Settings({
this.id = 227,
this.updatedAt = 0,
@ -414,6 +416,7 @@ class Settings {
this.volumeBoostCap,
this.downloadedOnlyMode = false,
this.algorithmWeights,
this.localFolders,
});
Settings.fromJson(Map<String, dynamic> json) {
@ -652,6 +655,7 @@ class Settings {
algorithmWeights = json['algorithmWeights'] != null
? AlgorithmWeights.fromJson(json['algorithmWeights'])
: null;
localFolders = json['localFolders'];
}
Map<String, dynamic> toJson() => {
@ -804,6 +808,7 @@ class Settings {
'downloadedOnlyMode': downloadedOnlyMode,
if (algorithmWeights != null)
'algorithmWeights': algorithmWeights!.toJson(),
'localFolders': localFolders,
};
}

File diff suppressed because it is too large Load diff

View file

@ -332,8 +332,14 @@ class _SubtitlesWidgetSearchState extends ConsumerState<SubtitlesWidgetSearch> {
try {
final subtitle = subtitles![index];
final storageProvider = StorageProvider();
final animeDir =
widget.chapter.archivePath != null &&
widget.chapter.manga.value?.source == "local"
? Directory(path.dirname(widget.chapter.archivePath!))
: null;
final chapterDirectory = (await storageProvider.getMangaChapterDirectory(
widget.chapter,
mangaMainDirectory: animeDir,
))!;
final subtitleFile = File(
path.join(

View file

@ -7,7 +7,7 @@ part of 'extensions_provider.dart';
// **************************************************************************
String _$getExtensionsStreamHash() =>
r'af34092ebf31c784010110af746e3ee2731297bd';
r'18790d3d4a7f52e5e7239c8726dcd09bb51d803a';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -73,6 +73,28 @@ class _SourcesScreenState extends ConsumerState<SourcesScreen> {
label: Text(context.l10n.show_extensions),
),
),
Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
l10n.other,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
),
),
SourceListTile(
source: Source(
name: "local",
lang: "",
itemType: widget.itemType,
),
itemType: widget.itemType,
),
],
);
}
@ -178,6 +200,34 @@ class _SourcesScreenState extends ConsumerState<SourcesScreen> {
item1.name!.compareTo(item2.name!),
order: GroupedListOrder.ASC,
),
SliverToBoxAdapter(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 12),
child: Row(
children: [
Text(
l10n.other,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
),
),
SourceListTile(
source: Source(
name: "local",
lang: "",
itemType: widget.itemType,
),
itemType: widget.itemType,
),
],
),
),
],
),
);

View file

@ -13,6 +13,9 @@ import 'package:mangayomi/utils/language.dart';
class SourceListTile extends StatelessWidget {
final ItemType itemType;
final Source source;
bool get isLocal => source.name == "local" && source.lang == "";
const SourceListTile({
super.key,
required this.source,
@ -24,21 +27,23 @@ class SourceListTile extends StatelessWidget {
return Consumer(
builder: (context, ref, child) => ListTile(
onTap: () {
final sources = isar.sources
.filter()
.idIsNotNull()
.and()
.itemTypeEqualTo(itemType)
.findAllSync();
isar.writeTxnSync(() {
for (var src in sources) {
isar.sources.putSync(
src
..lastUsed = src.id == source.id ? true : false
..updatedAt = DateTime.now().millisecondsSinceEpoch,
);
}
});
if (!isLocal) {
final sources = isar.sources
.filter()
.idIsNotNull()
.and()
.itemTypeEqualTo(itemType)
.findAllSync();
isar.writeTxnSync(() {
for (var src in sources) {
isar.sources.putSync(
src
..lastUsed = src.id == source.id ? true : false
..updatedAt = DateTime.now().millisecondsSinceEpoch,
);
}
});
}
context.push('/mangaHome', extra: (source, false));
},
leading: Container(
@ -73,7 +78,15 @@ class SourceListTile extends StatelessWidget {
),
],
),
title: Text(source.name!),
title: Text(
!isLocal
? source.name!
: "${context.l10n.local_source} ${source.itemType == ItemType.manga
? context.l10n.manga
: source.itemType == ItemType.anime
? context.l10n.anime
: context.l10n.novel}",
),
trailing: SizedBox(
width: 150,
child: Row(
@ -96,22 +109,23 @@ class SourceListTile extends StatelessWidget {
},
),
const SizedBox(width: 10),
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {
isar.writeTxnSync(
() => isar.sources.putSync(
source
..isPinned = !source.isPinned!
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
},
icon: Icon(
Icons.push_pin_outlined,
color: source.isPinned! ? context.primaryColor : null,
if (!isLocal)
IconButton(
padding: const EdgeInsets.all(0),
onPressed: () {
isar.writeTxnSync(
() => isar.sources.putSync(
source
..isPinned = !source.isPinned!
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
},
icon: Icon(
Icons.push_pin_outlined,
color: source.isPinned! ? context.primaryColor : null,
),
),
),
],
),
),

View file

@ -1,6 +1,10 @@
import 'dart:convert';
import 'dart:io'; // For I/O-operations
import 'package:epubx/epubx.dart';
import 'package:isar/isar.dart'; // Isar database package for local storage
import 'package:mangayomi/main.dart'; // Exposes the global `isar` instance
import 'package:mangayomi/models/settings.dart';
import 'package:mangayomi/utils/extensions/others.dart';
import 'package:path/path.dart' as p; // For manipulating file system paths
import 'package:bot_toast/bot_toast.dart'; // For Exceptions
import 'package:mangayomi/models/manga.dart'; // Has Manga model and ItemType enum
@ -10,6 +14,26 @@ import 'package:mangayomi/providers/storage_provider.dart'; // Provides storage
import 'package:riverpod_annotation/riverpod_annotation.dart'; // Annotations for code generation
part 'file_scanner.g.dart';
@riverpod
class LocalFoldersState extends _$LocalFoldersState {
@override
List<String> build() {
return isar.settings.getSync(227)!.localFolders ?? [];
}
void set(List<String> value) {
final settings = isar.settings.getSync(227)!;
state = value;
isar.writeTxnSync(
() => isar.settings.putSync(
settings
..localFolders = state
..updatedAt = DateTime.now().millisecondsSinceEpoch,
),
);
}
}
/// Scans `Mangayomi/local` folder (if exists) for Mangas/Animes and imports in library.
///
/// **Folder structure:**
@ -18,25 +42,37 @@ part 'file_scanner.g.dart';
/// Mangayomi/local/MangaName/Chapter1/Page1.jpg
/// Mangayomi/local/MangaName/Chapter2.cbz
/// Mangayomi/local/AnimeName/Episode1.mp4
/// Mangayomi/local/NovelName/NovelName.epub
/// ```
/// **Supported filetypes:** (taken from lib/modules/library/providers/local_archive.dart, line 98)
/// ```
/// Videotypes: mp4, mov, avi, flv, wmv, mpeg, mkv
/// Imagetypes: jpg, jpeg, png, webp
/// Archivetypes: cbz, zip, cbt, tar
/// Other types: epub
/// ```
@riverpod
Future<void> scanLocalLibrary(Ref ref) async {
// Get /local directory
final localDir = await _getLocalLibrary();
final localDir = await getLocalLibrary();
await _scanDirectory(ref, localDir);
final customDirs = ref.read(localFoldersStateProvider);
for (final dir in customDirs) {
await _scanDirectory(ref, Directory(dir));
}
}
Future<void> _scanDirectory(Ref ref, Directory? dir) async {
// Don't do anything if /local doesn't exist
if (localDir == null || !await localDir.exists()) return;
if (dir == null || !await dir.exists()) return;
final dateNow = DateTime.now().millisecondsSinceEpoch;
// Fetch all existing mangas in library that are in /local (or \local)
final List<Manga> existingMangas = await isar.mangas
.filter()
.sourceEqualTo("local")
.or()
.linkContains("Mangayomi/local")
.or()
.linkContains("Mangayomi\\local")
@ -84,7 +120,7 @@ Future<void> scanLocalLibrary(Ref ref) async {
}
// Iterate over each sub-directory (each representing a title, Manga or Anime)
await for (final folder in localDir.list()) {
await for (final folder in dir.list()) {
if (folder is! Directory) continue;
final title = p.basename(folder.path); // Anime/Manga title
String relativePath = _getRelativePath(folder.path);
@ -95,14 +131,19 @@ Future<void> scanLocalLibrary(Ref ref) async {
final files = children.whereType<File>().toList();
// Determine itemtype
final hasImagesFolders = subDirs.isNotEmpty;
final hasImagesFolders = subDirs
.where((e) => !e.path.endsWith("_subtitles"))
.isNotEmpty;
final hasArchives = files.any((f) => _isArchive(f.path));
final hasVideos = files.any((f) => _isVideo(f.path));
final hasEpubs = files.any((f) => _isEpub(f.path));
late ItemType itemType;
if (hasImagesFolders || hasArchives) {
itemType = ItemType.manga;
} else if (hasVideos) {
itemType = ItemType.anime;
} else if (hasEpubs) {
itemType = ItemType.novel;
} else {
continue; // nothing to import from this folder
}
@ -115,7 +156,7 @@ Future<void> scanLocalLibrary(Ref ref) async {
manga = mangaMap[relativePath]!;
} else {
manga = Manga(
favorite: true,
favorite: false,
source: 'local',
author: '',
artist: '',
@ -151,6 +192,26 @@ Future<void> scanLocalLibrary(Ref ref) async {
} else if (imageFiles.isEmpty && manga.customCoverImage != null) {
manga.customCoverImage = null;
}
final jsonFiles = files.where((f) => _isJson(f.path)).toList();
if (jsonFiles.isNotEmpty) {
try {
final str = await File(jsonFiles.first.path).readAsString();
final data = jsonDecode(str) as Map<String, dynamic>?;
manga.name = data?["name"];
manga.description = data?["description"];
manga.artist = data?["artist"];
manga.author = data?["author"];
manga.genre = data?["genre"]?.cast<String>();
manga.status = data?["status"] != null
? Status.values[data!["status"]]
: Status.unknown;
manga.lastUpdate = dateNow;
} catch (e) {
BotToast.showText(text: "Error reading metadata: $e");
}
}
processedMangas.add(manga);
// Scan chapters/episodes
@ -168,6 +229,11 @@ Future<void> scanLocalLibrary(Ref ref) async {
final videos = files.where((f) => _isVideo(f.path)).toList();
addNewChapters(videos, false);
}
if (hasEpubs) {
// Each .epub
final epubs = files.where((f) => _isEpub(f.path)).toList();
addNewChapters(epubs, false);
}
}
final changedMangas = <Manga>[];
@ -193,6 +259,8 @@ Future<void> scanLocalLibrary(Ref ref) async {
// Fetch all existing mangas in library that are in /local (or \local)
final savedMangas = await isar.mangas
.filter()
.sourceEqualTo("local")
.or()
.linkContains("Mangayomi/local")
.or()
.linkContains("Mangayomi\\local")
@ -223,20 +291,51 @@ Future<void> scanLocalLibrary(Ref ref) async {
final itemName = p.basename(p.dirname(chapterPath));
final manga = mangaByName[itemName];
if (manga != null) {
final chap = Chapter(
mangaId: manga.id,
name:
pathBool[1] // If Chapter is an image folder or archive/video
? p.basename(chapterPath)
: p.basenameWithoutExtension(chapterPath),
dateUpload: dateNow.toString(),
archivePath: chapterPath,
);
final chapterFile = File(chapterPath);
if (manga.itemType == ItemType.novel) {
final bytes = await chapterFile.readAsBytes();
final book = await EpubReader.readBook(bytes);
if (book.Content != null && book.Content!.Images != null) {
final coverImage =
book.Content!.Images!.containsKey("media/file0.png")
? book.Content!.Images!["media/file0.png"]!.Content
: book.Content!.Images!.values.first.Content;
manga.customCoverImage = coverImage;
saveManga++;
}
for (var chapter in book.Chapters ?? []) {
chaptersToSave.add(
Chapter(
mangaId: manga.id,
name: chapter.Title is String && chapter.Title.isEmpty
? "Book"
: chapter.Title,
archivePath: chapterPath,
downloadSize: chapterFile.existsSync()
? chapterFile.lengthSync().formattedFileSize()
: null,
)..manga.value = manga,
);
}
} else {
final chap = Chapter(
mangaId: manga.id,
name:
pathBool[1] // If Chapter is an image folder or archive/video
? p.basename(chapterPath)
: p.basenameWithoutExtension(chapterPath),
dateUpload: dateNow.toString(),
archivePath: chapterPath,
downloadSize: chapterFile.existsSync()
? chapterFile.lengthSync().formattedFileSize()
: null,
);
chaptersToSave.add(chap);
}
if (manga.lastUpdate != dateNow) {
manga.lastUpdate = dateNow;
saveManga++;
}
chaptersToSave.add(chap);
}
}
try {
@ -272,7 +371,7 @@ Future<void> scanLocalLibrary(Ref ref) async {
}
/// Returns the `/local` directory inside the app's default storage.
Future<Directory?> _getLocalLibrary() async {
Future<Directory?> getLocalLibrary() async {
try {
final dir = await StorageProvider().getDefaultDirectory();
return dir == null ? null : Directory(p.join(dir.path, 'local'));
@ -309,6 +408,12 @@ String _getRelativePath(dir) {
}
}
/// Returns if file is a json
bool _isJson(String path) {
final ext = p.extension(path).toLowerCase();
return ext == '.json';
}
/// Returns if file is an image
bool _isImage(String path) {
final ext = p.extension(path).toLowerCase();
@ -335,3 +440,9 @@ bool _isVideo(String path) {
};
return videoExtensions.contains(ext);
}
/// Returns if file is an epub or html
bool _isEpub(String path) {
final ext = p.extension(path).toLowerCase();
return ext == '.epub';
}

View file

@ -6,7 +6,7 @@ part of 'file_scanner.dart';
// RiverpodGenerator
// **************************************************************************
String _$scanLocalLibraryHash() => r'efbad9aa5fa4233e260a2e132389c23b40ef515a';
String _$scanLocalLibraryHash() => r'7fdedaa37917728d9f3b9d8f15090c94bdb34238';
/// Scans `Mangayomi/local` folder (if exists) for Mangas/Animes and imports in library.
///
@ -16,12 +16,14 @@ String _$scanLocalLibraryHash() => r'efbad9aa5fa4233e260a2e132389c23b40ef515a';
/// Mangayomi/local/MangaName/Chapter1/Page1.jpg
/// Mangayomi/local/MangaName/Chapter2.cbz
/// Mangayomi/local/AnimeName/Episode1.mp4
/// Mangayomi/local/NovelName/NovelName.epub
/// ```
/// **Supported filetypes:** (taken from lib/modules/library/providers/local_archive.dart, line 98)
/// ```
/// Videotypes: mp4, mov, avi, flv, wmv, mpeg, mkv
/// Imagetypes: jpg, jpeg, png, webp
/// Archivetypes: cbz, zip, cbt, tar
/// Other types: epub
/// ```
///
/// Copied from [scanLocalLibrary].
@ -39,5 +41,21 @@ final scanLocalLibraryProvider = AutoDisposeFutureProvider<void>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef ScanLocalLibraryRef = AutoDisposeFutureProviderRef<void>;
String _$localFoldersStateHash() => r'7cf7902ad34ee5ae018b2c9ac3849e822bc5f0b7';
/// See also [LocalFoldersState].
@ProviderFor(LocalFoldersState)
final localFoldersStateProvider =
AutoDisposeNotifierProvider<LocalFoldersState, List<String>>.internal(
LocalFoldersState.new,
name: r'localFoldersStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$localFoldersStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$LocalFoldersState = AutoDisposeNotifier<List<String>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:draggable_menu/draggable_menu.dart';
@ -32,6 +33,7 @@ import 'package:mangayomi/modules/widgets/custom_draggable_tabbar.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
import 'package:mangayomi/utils/utils.dart';
import 'package:mangayomi/utils/cached_network.dart';
@ -633,9 +635,13 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
value: 4,
child: Text(l10n.extension_settings),
),
PopupMenuItem<int>(
value: 5,
child: Text(l10n.export_metadata),
),
];
},
onSelected: (value) {
onSelected: (value) async {
switch (value) {
case 0:
widget.checkForUpdate(true);
@ -679,6 +685,58 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
extra: source,
);
break;
case 5:
try {
final result = await FilePicker.platform
.getDirectoryPath();
if (result != null) {
final client = MClient.init();
final coverFile = File(
p.join(result, "cover.jpg"),
);
final metadataFile = File(
p.join(result, "metadata.json"),
);
final headers =
widget.manga!.isLocalArchive!
? null
: ref.read(
headersProvider(
source: widget.manga!.source!,
lang: widget.manga!.lang!,
sourceId:
widget.manga!.sourceId,
),
);
final imageUrl = toImgUrl(
widget.manga!.customCoverFromTracker ??
widget.manga!.imageUrl ??
"",
);
final res = await client.get(
Uri.parse(imageUrl),
headers: headers,
);
await coverFile.writeAsBytes(
res.bodyBytes,
);
await metadataFile.writeAsString(
jsonEncode({
"name": widget.manga!.name,
"description":
widget.manga!.description,
"artist": widget.manga!.artist,
"author": widget.manga!.author,
"genre": widget.manga!.genre,
"status": widget.manga!.status.index,
}),
);
botToast(l10n.exported);
}
} catch (e) {
botToast("Failed to export metadata: $e");
}
break;
}
},
),
@ -1454,7 +1512,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
),
],
),
isLocalArchive ? _action() : _actionFavouriteAndWebview(),
_actionFavouriteAndWebview(),
Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
@ -1875,53 +1933,58 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
child: Row(
children: [
Expanded(child: widget.action!),
Expanded(child: _smartUpdateDays()),
if (!isLocalArchive) Expanded(child: _smartUpdateDays()),
Expanded(
child: widget.itemType == ItemType.novel
? SizedBox.shrink()
: _action(),
),
Expanded(
child: SizedBox(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
elevation: 0,
),
onPressed: () async {
final manga = widget.manga!;
if (!isLocalArchive)
Expanded(
child: SizedBox(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
elevation: 0,
),
onPressed: () async {
final manga = widget.manga!;
final source = getSource(
widget.manga!.lang!,
widget.manga!.source!,
widget.manga!.sourceId,
);
final url =
"${source!.baseUrl}${widget.manga!.link!.getUrlWithoutDomain}";
final source = getSource(
widget.manga!.lang!,
widget.manga!.source!,
widget.manga!.sourceId,
);
final url =
"${source!.baseUrl}${widget.manga!.link!.getUrlWithoutDomain}";
Map<String, dynamic> data = {
'url': url,
'sourceId': source.id.toString(),
'title': manga.name!,
};
context.push("/mangawebview", extra: data);
},
child: Column(
children: [
Icon(Icons.public, size: 20, color: context.secondaryColor),
const SizedBox(height: 4),
Text(
'WebView',
style: TextStyle(
fontSize: 11,
Map<String, dynamic> data = {
'url': url,
'sourceId': source.id.toString(),
'title': manga.name!,
};
context.push("/mangawebview", extra: data);
},
child: Column(
children: [
Icon(
Icons.public,
size: 20,
color: context.secondaryColor,
),
),
],
const SizedBox(height: 4),
Text(
'WebView',
style: TextStyle(
fontSize: 11,
color: context.secondaryColor,
),
),
],
),
),
),
),
),
],
),
);

View file

@ -160,37 +160,36 @@ class _MangaDetailsViewState extends ConsumerState<MangaDetailsView> {
},
),
body: MangaDetailView(
titleDescription: isLocalArchive
? Container()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.manga.author!,
style: const TextStyle(fontWeight: FontWeight.w500),
titleDescription: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.manga.author ?? "Unknown",
style: const TextStyle(fontWeight: FontWeight.w500),
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Icon(getMangaStatusIcon(widget.manga.status), size: 14),
const SizedBox(width: 4),
Text(getMangaStatusName(widget.manga.status, context)),
if (!isLocalArchive) const Text(''),
if (!isLocalArchive) Text(widget.manga.source!),
if (!isLocalArchive)
Text(' (${widget.manga.lang!.toUpperCase()})'),
if (!isLocalArchive && !widget.sourceExist)
const Padding(
padding: EdgeInsets.all(3),
child: Icon(
Icons.warning_amber,
color: Colors.deepOrangeAccent,
size: 14,
),
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Icon(getMangaStatusIcon(widget.manga.status), size: 14),
const SizedBox(width: 4),
Text(getMangaStatusName(widget.manga.status, context)),
const Text(''),
Text(widget.manga.source!),
Text(' (${widget.manga.lang!.toUpperCase()})'),
if (!widget.sourceExist)
const Padding(
padding: EdgeInsets.all(3),
child: Icon(
Icons.warning_amber,
color: Colors.deepOrangeAccent,
size: 14,
),
),
],
),
],
),
],
),
],
),
action: widget.manga.favorite!
? SizedBox(
child: ElevatedButton(

View file

@ -22,7 +22,8 @@ Future<dynamic> updateMangaDetail(
}) async {
try {
final manga = isar.mangas.getSync(mangaId!);
if (manga!.chapters.isNotEmpty && isInit) {
if ((manga!.isLocalArchive ?? false) ||
(manga.chapters.isNotEmpty && isInit)) {
return;
}
final source = getSource(manga.lang!, manga.source!, manga.sourceId);

View file

@ -6,7 +6,7 @@ part of 'update_manga_detail_providers.dart';
// RiverpodGenerator
// **************************************************************************
String _$updateMangaDetailHash() => r'6e4faa1fe453df67182ff6698f1ca54a7fff2bea';
String _$updateMangaDetailHash() => r'3b15af65efba4f27e0fe990c903e66973ac31af3';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -146,6 +146,21 @@ class ChapterListTileWidget extends ConsumerWidget {
),
],
),
if (chapter.downloadSize != null)
Row(
children: [
const Text(''),
Text(
chapter.downloadSize!,
style: TextStyle(
fontSize: 11,
color: context.isLight
? Colors.black.withValues(alpha: 0.4)
: Colors.white.withValues(alpha: 0.3),
),
),
],
),
],
),
trailing:

View file

@ -63,7 +63,8 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
? 2
: 0;
late Source source = widget.source;
late List<dynamic> filters = getFilterList(source: source);
late bool isLocal = source.name == "local" && source.lang == "";
late List<dynamic> filters = isLocal ? [] : getFilterList(source: source);
final List<MManga> _mangaList = [];
List<TypeMangaSelector> _types(BuildContext context) {
final l10n = l10nLocalizations(context)!;
@ -127,8 +128,10 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
AsyncValue<MPages?>? _getManga;
int _length = 0;
bool _isFiltering = false;
late final supportsLatest = ref.watch(supportsLatestProvider(source: source));
late final filterList = getFilterList(source: source);
late final supportsLatest = isLocal
? true
: ref.watch(supportsLatestProvider(source: source));
late final filterList = isLocal ? [] : getFilterList(source: source);
@override
Widget build(BuildContext context) {
if (_selectedIndex == 2 && (_isSearch && _query.isNotEmpty) ||
@ -161,7 +164,15 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${source.name}"),
Text(
!isLocal
? "${source.name}"
: "${context.l10n.local_source} ${source.itemType == ItemType.manga
? context.l10n.manga
: source.itemType == ItemType.anime
? context.l10n.anime
: context.l10n.novel}",
),
source.notes != null && source.notes!.isNotEmpty
? SizedBox(
height: 20,
@ -267,44 +278,45 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
},
onSelected: (value) {},
),
PopupMenuButton(
popUpAnimationStyle: popupAnimationStyle,
itemBuilder: (context) {
return [
PopupMenuItem<int>(
value: 0,
child: Text(context.l10n.open_in_browser),
),
PopupMenuItem<int>(
value: 1,
child: Text(context.l10n.settings),
),
];
},
onSelected: (value) async {
if (value == 0) {
final baseUrl = ref.watch(
sourceBaseUrlProvider(source: source),
);
Map<String, dynamic> data = {
'url': baseUrl,
'sourceId': source.id.toString(),
'title': '',
};
context.push("/mangawebview", extra: data);
} else {
final res = await context.push(
'/extension_detail',
extra: source,
);
if (res != null && mounted) {
setState(() {
source = res as Source;
});
if (!isLocal)
PopupMenuButton(
popUpAnimationStyle: popupAnimationStyle,
itemBuilder: (context) {
return [
PopupMenuItem<int>(
value: 0,
child: Text(context.l10n.open_in_browser),
),
PopupMenuItem<int>(
value: 1,
child: Text(context.l10n.settings),
),
];
},
onSelected: (value) async {
if (value == 0) {
final baseUrl = ref.watch(
sourceBaseUrlProvider(source: source),
);
Map<String, dynamic> data = {
'url': baseUrl,
'sourceId': source.id.toString(),
'title': '',
};
context.push("/mangawebview", extra: data);
} else {
final res = await context.push(
'/extension_detail',
extra: source,
);
if (res != null && mounted) {
setState(() {
source = res as Source;
});
}
}
}
},
),
},
),
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(AppBar().preferredSize.height * 0.8),

View file

@ -269,43 +269,33 @@ class AppearanceScreen extends ConsumerWidget {
),
)
.toList();
return Flexible(
return Expanded(
child: Scrollbar(
interactive: true,
thickness: 12,
radius: const Radius.circular(10),
controller: controller,
child: CustomScrollView(
controller: controller,
slivers: [
SliverPadding(
padding: const EdgeInsets.all(0),
sliver: RadioGroup(
groupValue: appFontFamily,
onChanged: (value) {
ref
.read(
appFontFamilyProvider.notifier,
)
.set(value);
Navigator.pop(context);
},
child: SuperSliverList.builder(
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
return RadioListTile(
dense: true,
contentPadding:
const EdgeInsets.all(0),
value: value.value().fontFamily,
title: Text(value.key),
);
},
),
),
),
],
child: RadioGroup<String?>(
groupValue: appFontFamily,
onChanged: (value) {
ref
.read(appFontFamilyProvider.notifier)
.set(value);
Navigator.pop(context);
},
child: SuperListView.builder(
controller: controller,
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
return RadioListTile<String?>(
dense: true,
contentPadding: const EdgeInsets.all(0),
value: value.value().fontFamily,
title: Text(value.key),
);
},
),
),
),
);

View file

@ -1,5 +1,8 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mangayomi/l10n/generated/app_localizations.dart';
import 'package:mangayomi/modules/library/providers/file_scanner.dart';
import 'package:mangayomi/modules/more/settings/downloads/providers/downloads_state_provider.dart';
import 'package:mangayomi/providers/l10n_providers.dart';
import 'package:mangayomi/utils/extensions/build_context_extensions.dart';
@ -18,6 +21,7 @@ class _DownloadsScreenState extends ConsumerState<DownloadsScreen> {
final saveAsCBZArchiveState = ref.watch(saveAsCBZArchiveStateProvider);
final onlyOnWifiState = ref.watch(onlyOnWifiStateProvider);
final concurrentDownloads = ref.watch(concurrentDownloadsStateProvider);
final localFolders = ref.watch(localFoldersStateProvider);
final l10n = l10nLocalizations(context);
return Scaffold(
appBar: AppBar(title: Text(l10n!.downloads)),
@ -106,6 +110,291 @@ class _DownloadsScreenState extends ConsumerState<DownloadsScreen> {
style: TextStyle(fontSize: 11, color: context.secondaryColor),
),
),
ListTile(
onTap: () async => ref.read(scanLocalLibraryProvider.future),
title: Text(context.l10n.rescan_local_folder),
),
ListTile(
onTap: () async {
final result = await FilePicker.platform.getDirectoryPath();
if (result != null) {
final temp = localFolders.toList();
temp.add(result);
ref.read(localFoldersStateProvider.notifier).set(temp);
}
},
title: Text(context.l10n.add_local_folder),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Text(
context.l10n.local_folder,
style: TextStyle(
fontSize: 13,
color: context.primaryColor,
),
),
const SizedBox(width: 20),
OutlinedButton.icon(
onPressed: () => _showHelpDialog(context),
label: const Icon(Icons.question_mark),
),
],
),
),
FutureBuilder(
future: getLocalLibrary(),
builder: (context, snapshot) => snapshot.data?.path != null
? _buildLocalFolder(
l10n,
localFolders,
snapshot.data!.path,
isDefault: true,
)
: Container(),
),
...localFolders.map(
(e) => _buildLocalFolder(l10n, localFolders, e),
),
],
),
),
],
),
),
);
}
void _showHelpDialog(BuildContext context) {
final data = (
"LocalFolder",
[
(
"MangaName",
[
("cover.jpg", Icons.image_outlined),
(
"Chapter1",
[
("Page1.jpg", Icons.image_outlined),
("Page2.jpeg", Icons.image_outlined),
("Page3.png", Icons.image_outlined),
("Page4.webp", Icons.image_outlined),
],
),
("Chapter2.cbz", Icons.folder_zip_outlined),
("Chapter3.zip", Icons.folder_zip_outlined),
("Chapter4.cbt", Icons.folder_zip_outlined),
("Chapter5.tar", Icons.folder_zip_outlined),
],
),
(
"AnimeName",
[
("cover.jpg", Icons.image_outlined),
("Episode1.mp4", Icons.video_file_outlined),
(
"Episode1_subtitles",
[
("en.srt", Icons.subtitles_outlined),
("de.srt", Icons.subtitles_outlined),
],
),
("Episode2.mov", Icons.video_file_outlined),
("Episode3.avi", Icons.video_file_outlined),
("Episode4.flv", Icons.video_file_outlined),
("Episode5.wmv", Icons.video_file_outlined),
("Episode6.mpeg", Icons.video_file_outlined),
("Episode7.mkv", Icons.video_file_outlined),
],
),
(
"NovelName",
[
("cover.jpg", Icons.image_outlined),
("NovelName.epub", Icons.book_outlined),
],
),
],
);
Widget buildSubFolder((String, dynamic) data, int level) {
if (data.$2 is List) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text.rich(
TextSpan(
children: [
for (int i = 1; i < level; i++)
const WidgetSpan(child: SizedBox(width: 20)),
if (level > 0)
WidgetSpan(child: Icon(Icons.subdirectory_arrow_right)),
WidgetSpan(child: Icon(Icons.folder)),
const WidgetSpan(child: SizedBox(width: 5)),
TextSpan(text: data.$1),
],
),
),
...(data.$2 as List<(String, dynamic)>).map(
(e) => buildSubFolder(e, level + 1),
),
],
);
}
return Text.rich(
TextSpan(
children: [
for (int i = 1; i < level; i++)
const WidgetSpan(child: SizedBox(width: 20)),
if (level > 0)
WidgetSpan(child: Icon(Icons.subdirectory_arrow_right)),
WidgetSpan(child: Icon(data.$2 as IconData)),
const WidgetSpan(child: SizedBox(width: 5)),
TextSpan(text: data.$1),
],
),
);
}
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(context.l10n.local_folder_structure),
content: SizedBox(
width: context.width(0.6),
height: context.height(0.8),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(child: buildSubFolder(data, 0)),
),
),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(context.l10n.cancel),
),
],
),
],
);
},
);
},
);
}
Widget _buildLocalFolder(
AppLocalizations l10n,
List<String> localFolders,
String folder, {
bool isDefault = false,
}) {
return Padding(
key: Key('folder_${folder.hashCode}'),
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: null,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Icon(Icons.label_outline_rounded),
const SizedBox(width: 10),
Expanded(child: Text(folder)),
],
),
),
if (!isDefault)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text(l10n.delete),
content: Text("${l10n.delete} $folder"),
actions: [
Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(l10n.cancel),
),
const SizedBox(width: 15),
TextButton(
onPressed: () {
final temp = localFolders
.toList();
temp.removeAt(
temp.indexOf(folder),
);
ref
.read(
localFoldersStateProvider
.notifier,
)
.set(temp);
Navigator.pop(context);
},
child: Text(l10n.ok),
),
],
),
],
);
},
);
},
);
},
icon: const Icon(Icons.delete_outlined),
),
],
),
],
),
],
),
),

View file

@ -328,58 +328,69 @@ Future<void> pushToMangaReaderDetail({
bool addToFavourite = false,
}) async {
int? mangaId;
if (archiveId == null) {
final manga =
mangaM ??
Manga(
imageUrl: getManga!.imageUrl,
name: getManga.name!.trim().trimLeft().trimRight(),
genre: getManga.genre?.map((e) => e.toString()).toList() ?? [],
author: getManga.author ?? "",
status: getManga.status ?? Status.unknown,
description: getManga.description ?? "",
link: getManga.link,
source: source,
lang: lang,
lastUpdate: 0,
itemType: itemType ?? ItemType.manga,
artist: getManga.artist ?? '',
sourceId: sourceId,
);
final empty = isar.mangas
.filter()
.langEqualTo(lang)
.nameEqualTo(manga.name)
.sourceEqualTo(manga.source)
.isEmptySync();
if (empty) {
isar.writeTxnSync(() {
isar.mangas.putSync(
manga..updatedAt = DateTime.now().millisecondsSinceEpoch,
);
});
} else {
isar.writeTxnSync(() {
isar.mangas.putSync(manga);
});
}
mangaId = isar.mangas
.filter()
.isLocalArchiveEqualTo(true)
.sourceEqualTo("local")
.nameEqualTo(getManga?.name)
.findFirstSync()
?.id;
mangaId = isar.mangas
.filter()
.langEqualTo(lang)
.nameEqualTo(manga.name)
.sourceEqualTo(manga.source)
.findAllSync()
.firstWhere(
(element) =>
element.sourceId == null ? true : element.sourceId == sourceId,
)
.id!;
} else {
mangaId = archiveId;
if (mangaId == null) {
if (archiveId == null) {
final manga =
mangaM ??
Manga(
imageUrl: getManga!.imageUrl,
name: getManga.name!.trim().trimLeft().trimRight(),
genre: getManga.genre?.map((e) => e.toString()).toList() ?? [],
author: getManga.author ?? "",
status: getManga.status ?? Status.unknown,
description: getManga.description ?? "",
link: getManga.link,
source: source,
lang: lang,
lastUpdate: 0,
itemType: itemType ?? ItemType.manga,
artist: getManga.artist ?? '',
sourceId: sourceId,
);
final empty = isar.mangas
.filter()
.langEqualTo(lang)
.nameEqualTo(manga.name)
.sourceEqualTo(manga.source)
.isEmptySync();
if (empty) {
isar.writeTxnSync(() {
isar.mangas.putSync(
manga..updatedAt = DateTime.now().millisecondsSinceEpoch,
);
});
} else {
isar.writeTxnSync(() {
isar.mangas.putSync(manga);
});
}
mangaId = isar.mangas
.filter()
.langEqualTo(lang)
.nameEqualTo(manga.name)
.sourceEqualTo(manga.source)
.findAllSync()
.firstWhere(
(element) =>
element.sourceId == null ? true : element.sourceId == sourceId,
)
.id!;
} else {
mangaId = archiveId;
}
}
final mang = isar.mangas.getSync(mangaId);
if (mang!.sourceId == null) {
if (mang!.sourceId == null && !(mang.isLocalArchive ?? false)) {
isar.writeTxnSync(() {
isar.mangas.putSync(mang..sourceId = sourceId);
});

View file

@ -6,7 +6,7 @@ part of 'aniskip.dart';
// RiverpodGenerator
// **************************************************************************
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
/// See also [AniSkip].
@ProviderFor(AniSkip)

View file

@ -1,5 +1,11 @@
import 'dart:math';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -12,6 +18,27 @@ Future<MPages?> getLatestUpdates(
required Source source,
required int page,
}) async {
if (source.name == "local" && source.lang == "") {
final result =
(await isar.mangas
.filter()
.itemTypeEqualTo(source.itemType)
.group(
(q) => q
.sourceEqualTo("local")
.or()
.linkContains("Mangayomi/local")
.or()
.linkContains("Mangayomi\\local"),
)
.sortByDateAddedDesc()
.offset(max(0, page - 1) * 50)
.limit(50)
.findAll())
.map((e) => MManga(name: e.name))
.toList();
return MPages(list: result, hasNextPage: true);
}
return getExtensionService(
source,
ref.read(androidProxyServerStateProvider),

View file

@ -6,7 +6,7 @@ part of 'get_latest_updates.dart';
// RiverpodGenerator
// **************************************************************************
String _$getLatestUpdatesHash() => r'fd4ece1d796e079a469e5f80f456ee821ff0bc03';
String _$getLatestUpdatesHash() => r'7a3c06c469c77ec933cf2f4dd7d39780d993f0ea';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,5 +1,11 @@
import 'dart:math';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -12,6 +18,27 @@ Future<MPages?> getPopular(
required Source source,
required int page,
}) async {
if (source.name == "local" && source.lang == "") {
final result =
(await isar.mangas
.filter()
.itemTypeEqualTo(source.itemType)
.group(
(q) => q
.sourceEqualTo("local")
.or()
.linkContains("Mangayomi/local")
.or()
.linkContains("Mangayomi\\local"),
)
.sortByName()
.offset(max(0, page - 1) * 50)
.limit(50)
.findAll())
.map((e) => MManga(name: e.name))
.toList();
return MPages(list: result, hasNextPage: true);
}
return getExtensionService(
source,
ref.read(androidProxyServerStateProvider),

View file

@ -6,7 +6,7 @@ part of 'get_popular.dart';
// RiverpodGenerator
// **************************************************************************
String _$getPopularHash() => r'5fd933ce7e2b9c2dd113b7642ed54c1a1196f638';
String _$getPopularHash() => r'f169b6a9ba76d9dd9237ba9c21805151a1419843';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -32,9 +32,13 @@ Future<(List<Video>, bool, List<String>, Directory?)> getVideoList(
);
List<String> infoHashes = [];
if (await File(mp4animePath).exists() || isLocalArchive) {
final animeDir =
episode.archivePath != null && episode.manga.value?.source == "local"
? Directory(p.dirname(episode.archivePath!))
: null;
final chapterDirectory = (await storageProvider.getMangaChapterDirectory(
episode,
mangaMainDirectory: mangaDirectory,
mangaMainDirectory: animeDir ?? mangaDirectory,
))!;
final path = isLocalArchive ? episode.archivePath : mp4animePath;
final subtitlesDir = Directory(

View file

@ -6,7 +6,7 @@ part of 'get_video_list.dart';
// RiverpodGenerator
// **************************************************************************
String _$getVideoListHash() => r'0b35633b9758cd633cc826801568aa81fbb27b61';
String _$getVideoListHash() => r'c54f7294e15eeede933a6e04cd9b761d82b5f74c';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,5 +1,11 @@
import 'dart:math';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -14,6 +20,27 @@ Future<MPages?> search(
required int page,
required List<dynamic> filterList,
}) async {
if (source.name == "local" && source.lang == "") {
final result =
(await isar.mangas
.filter()
.itemTypeEqualTo(source.itemType)
.group(
(q) => q
.sourceEqualTo("local")
.or()
.linkContains("Mangayomi/local")
.or()
.linkContains("Mangayomi\\local"),
)
.nameContains(query, caseSensitive: false)
.offset(max(0, page - 1) * 50)
.limit(50)
.findAll())
.map((e) => MManga(name: e.name))
.toList();
return MPages(list: result, hasNextPage: true);
}
return getExtensionService(
source,
ref.read(androidProxyServerStateProvider),

View file

@ -6,7 +6,7 @@ part of 'search.dart';
// RiverpodGenerator
// **************************************************************************
String _$searchHash() => r'b08d5a4b6e7d285830af7e5388b06fa61f175ede';
String _$searchHash() => r'b6bac0a3af58547bb93356f3c00a04135cd5a891';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,6 +1,10 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/eval/model/m_manga.dart';
import 'package:mangayomi/eval/model/m_pages.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/manga.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
@ -14,6 +18,26 @@ Future<MPages?> search(
required int page,
required List<dynamic> filterList,
}) async {
if (source.name == "local" && source.lang == "") {
final result =
(await isar.mangas
.filter()
.group(
(q) => q
.sourceEqualTo("local")
.or()
.linkContains("Mangayomi/local")
.or()
.linkContains("Mangayomi\\local"),
)
.nameContains(query, caseSensitive: false)
.offset(page * 50)
.limit(50)
.findAll())
.map((e) => MManga(name: e.name))
.toList();
return MPages(list: result, hasNextPage: true);
}
return getExtensionService(
source,
ref.read(androidProxyServerStateProvider),

View file

@ -6,7 +6,7 @@ part of 'search_.dart';
// RiverpodGenerator
// **************************************************************************
String _$searchHash() => r'b08d5a4b6e7d285830af7e5388b06fa61f175ede';
String _$searchHash() => r'e23c6dd56549ffdfc89b5fcfa43719d90ca1760e';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -6,7 +6,7 @@ part of 'anilist.dart';
// RiverpodGenerator
// **************************************************************************
String _$anilistHash() => r'89e86869bd2b807e08beb8b7af507809d8bb9895';
String _$anilistHash() => r'b3c56b172308ecd98c4dd9fb89d17ccc36487754';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -164,7 +164,7 @@ class TraktTv extends _$TraktTv implements BaseTracker {
"movies";
final url = Uri.parse(
'$_baseApiUrl/sync/history/${isMovie ? "movies" : "shows"}/${track.mediaId}',
).replace(queryParameters: {"extended": "full"});
).replace(queryParameters: {"extended": "full", "page": "1", "limit": "3000"});
final result = await _makeGetRequest(url, accessToken);
final data = jsonDecode(result.body) as List?;
if (data?.isNotEmpty ?? false) {
@ -239,7 +239,7 @@ class TraktTv extends _$TraktTv implements BaseTracker {
final isMovie =
track.trackingUrl?.replaceAll("https://trakt.tv/", "").split("/")[0] ==
"movies";
final urlRemove = Uri.parse(
/*final urlRemove = Uri.parse(
"$_baseApiUrl/sync/history/remove",
).replace(queryParameters: {'clientId': _clientId});
final bodyRemove = isMovie
@ -257,7 +257,7 @@ class TraktTv extends _$TraktTv implements BaseTracker {
},
],
};
await _makePostRequest(urlRemove, accessToken, bodyRemove);
await _makePostRequest(urlRemove, accessToken, bodyRemove);*/
final url = Uri.parse(
"$_baseApiUrl/sync/history",
).replace(queryParameters: {'extended': 'full', 'clientId': _clientId});
@ -265,7 +265,7 @@ class TraktTv extends _$TraktTv implements BaseTracker {
? {
'movies': [
{
'watched_at': DateTime.now().toIso8601String(),
'watched_at': DateTime.timestamp().toIso8601String(),
'ids': {'trakt': track.mediaId},
},
],
@ -273,7 +273,6 @@ class TraktTv extends _$TraktTv implements BaseTracker {
: {
'shows': [
{
'watched_at': DateTime.now().toIso8601String(),
'ids': {'trakt': track.mediaId},
'seasons': [
{
@ -281,7 +280,7 @@ class TraktTv extends _$TraktTv implements BaseTracker {
'episodes': [
for (int i = 1; i <= (track.lastChapterRead ?? 1); i++)
{
'watched_at': DateTime.now().toIso8601String(),
'watched_at': DateTime.timestamp().toIso8601String(),
'number': i,
},
],

View file

@ -6,7 +6,7 @@ part of 'trakt_tv.dart';
// RiverpodGenerator
// **************************************************************************
String _$traktTvHash() => r'd852a7d96511637bf565cbcf6e958397740158fd';
String _$traktTvHash() => r'269f0b865c39188f083dbc7dcad9652ee9e31efa';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
@ -27,11 +27,13 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
RustLibApi? api,
BaseHandler? handler,
ExternalLibrary? externalLibrary,
bool forceSameCodegenVersion = true,
}) async {
await instance.initImpl(
api: api,
handler: handler,
externalLibrary: externalLibrary,
forceSameCodegenVersion: forceSameCodegenVersion,
);
}
@ -63,7 +65,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
kDefaultExternalLibraryLoaderConfig;
@override
String get codegenVersion => '2.10.0';
String get codegenVersion => '2.11.1';
@override
int get rustContentHash => 885218533;

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import

View file

@ -1,10 +1,12 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';
import 'package:extended_image/extended_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:mangayomi/modules/manga/reader/u_chap_data_preload.dart';
import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart';
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
@ -14,6 +16,16 @@ import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
extension FileFormatter on num {
String formattedFileSize({bool base1024 = true}) {
final base = base1024 ? 1024 : 1000;
if (this <= 0) return "0";
final units = ["B", "kB", "MB", "GB", "TB"];
int digitGroups = (log(this) / log(base)).round();
return "${NumberFormat("#,##0.#").format(this / pow(base, digitGroups))} ${units[digitGroups]}";
}
}
extension LetExtension<T> on T {
R let<R>(R Function(T) block) {
return block(this);

View file

@ -349,10 +349,10 @@ packages:
dependency: "direct main"
description:
name: d4rt
sha256: "802f4fed100431335d87912e97132117ad7dd2a455958c92b94995317d92402f"
sha256: "59521e738b85c2db8240775fec9dbeda488d58bf6c1c27198baba54bbe623c5a"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
version: "0.1.5"
dart_style:
dependency: transitive
description:
@ -549,10 +549,10 @@ packages:
description:
path: "."
ref: main
resolved-ref: "67e99dd4547bdba15a49d53ab61f50397b194804"
resolved-ref: "5032c0ac4d257b0fad8d3c6cd05f6d15889fc1e5"
url: "https://github.com/Schnitzel5/flutter-discord-rpc.git"
source: git
version: "1.0.4"
version: "1.0.5"
flutter_inappwebview:
dependency: "direct main"
description:
@ -667,10 +667,10 @@ packages:
dependency: "direct main"
description:
name: flutter_rust_bridge
sha256: b416ff56002789e636244fb4cc449f587656eff995e5a7169457eb0593fcaddb
sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e"
url: "https://pub.dev"
source: hosted
version: "2.10.0"
version: "2.11.1"
flutter_svg:
dependency: transitive
description:
@ -1012,10 +1012,10 @@ packages:
dependency: "direct main"
description:
name: js_packer
sha256: f45ffa90165a810d7134f0b96b54068e4aac9d80a8b181eafa3978ec6dbc66a3
sha256: "1d31c768594787acbab3573573e751fa6cf1e085ddacfd9cafd3d74eddb27060"
url: "https://pub.dev"
source: hosted
version: "0.0.5"
version: "0.0.6"
json_annotation:
dependency: transitive
description:
@ -2230,5 +2230,5 @@ packages:
source: hosted
version: "2.2.2"
sdks:
dart: ">=3.8.1 <4.0.0"
dart: ">=3.9.2 <4.0.0"
flutter: ">=3.35.0"

View file

@ -4,7 +4,7 @@ publish_to: "none"
version: 0.6.5+87
environment:
sdk: ^3.8.1
sdk: ^3.9.2
dependencies:
flutter:
@ -58,7 +58,7 @@ dependencies:
ffi: ^2.1.3
ffigen: 19.1.0
http_interceptor: ^2.0.0
js_packer: ^0.0.5
js_packer: ^0.0.6
flutter_qjs:
git:
url: https://github.com/kodjodevf/flutter_qjs.git
@ -71,7 +71,7 @@ dependencies:
re_highlight: ^0.0.3
json_view: ^0.4.2
super_sliver_list: ^0.4.1
flutter_rust_bridge: 2.10.0
flutter_rust_bridge: ^2.11.1
rust_lib_mangayomi:
path: rust_builder
pseudom: ^1.0.1
@ -95,7 +95,7 @@ dependencies:
git:
url: https://github.com/kodjodevf/epubx.dart.git
ref: dev
d4rt: 0.1.3
d4rt: ^0.1.5
hive: ^2.2.3
hive_flutter: ^1.1.0
flutter_discord_rpc_fork:
@ -119,7 +119,7 @@ dev_dependencies:
build_runner: ^2.4.6
riverpod_generator: ^2.6.5
flutter_launcher_icons: ^0.14.3
isar_generator:
isar_generator:
path: plugins/isar_generator
flutter_lints: ^5.0.0
freezed: ^3.0.0
@ -161,4 +161,3 @@ inno_bundle:
- french
- german
admin: false
version: 0.6.5

View file

@ -17,12 +17,12 @@
"version": "0.6.5",
"versionDate": "2025-08-29T15:38:32Z",
"versionDescription": "What's Changed\r\n* Show correct version number on Windows by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/542\r\n* re\u2022added full upload and download by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/544\r\n* Fix MAL & Kitsu \"No Cover\" Exception by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/555\r\n* allow to swipe pages even if zoomed in by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/553\r\n* added Anibrain recommendations by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/552\r\n* move history to more screen if hidden by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/549\r\n* added downloaded only mode by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/551\r\n* added quick access to source settings by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/550\r\n* added missing arb entry by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/556\r\n* Refactor select bar widget and unify manga read state logic by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/536\r\n* enhanced mpv player by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/540\r\n* added support for Mihon extensions via ApkBridge by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/559\r\n* fixed local subtitles by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/565\r\n* enhanced calendar by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/564\r\n* added subtitles search by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/560\r\n* added watch order by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/562\r\n* changed from labels to icons by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/566\r\n* added Simkl tracker by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/561\r\n* add option to download online subtitles by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/567\r\n* fixed Trakt refresh token by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/569\r\n* added button link to the apk by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/570\r\n* added option to turn on/off mpv hardware acceleration by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/571\r\n\r\n\r\n**Full Changelog**: https://github.com/kodjodevf/mangayomi/compare/v0.6.35...v0.6.5",
"downloadURL": null,
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.6.5/Mangayomi-v0.6.5-ios.ipa",
"localizedDescription": "Mangayomi is an open-source Flutter app for reading manga, novels, and watching anime across multiple platforms.",
"iconURL": "https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/icons/icon_default.webp",
"tintColor": "EF4444",
"category": "entertainment",
"size": 59596148,
"size": 44527742,
"screenshotURLs": [
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_0_default.webp",
"https://raw.githubusercontent.com/kodjodevf/mangayomi/refs/heads/main/repo/images/screenshots/image_1_default.webp",
@ -34,7 +34,7 @@
"date": "2025-08-29T15:38:32Z",
"localizedDescription": "What's Changed\r\n* Show correct version number on Windows by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/542\r\n* re\u2022added full upload and download by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/544\r\n* Fix MAL & Kitsu \"No Cover\" Exception by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/555\r\n* allow to swipe pages even if zoomed in by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/553\r\n* added Anibrain recommendations by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/552\r\n* move history to more screen if hidden by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/549\r\n* added downloaded only mode by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/551\r\n* added quick access to source settings by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/550\r\n* added missing arb entry by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/556\r\n* Refactor select bar widget and unify manga read state logic by @NBA2K1 in https://github.com/kodjodevf/mangayomi/pull/536\r\n* enhanced mpv player by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/540\r\n* added support for Mihon extensions via ApkBridge by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/559\r\n* fixed local subtitles by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/565\r\n* enhanced calendar by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/564\r\n* added subtitles search by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/560\r\n* added watch order by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/562\r\n* changed from labels to icons by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/566\r\n* added Simkl tracker by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/561\r\n* add option to download online subtitles by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/567\r\n* fixed Trakt refresh token by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/569\r\n* added button link to the apk by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/570\r\n* added option to turn on/off mpv hardware acceleration by @Schnitzel5 in https://github.com/kodjodevf/mangayomi/pull/571\r\n\r\n\r\n**Full Changelog**: https://github.com/kodjodevf/mangayomi/compare/v0.6.35...v0.6.5",
"downloadURL": "https://github.com/kodjodevf/mangayomi/releases/download/v0.6.5/Mangayomi-v0.6.5-ios.ipa",
"size": 59596148
"size": 44527742
},
{
"version": "0.6.35",

38
rust/Cargo.lock generated
View file

@ -67,20 +67,19 @@ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_log-sys"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
[[package]]
name = "android_logger"
version = "0.13.3"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f"
checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3"
dependencies = [
"android_log-sys",
"env_logger",
"env_filter",
"log",
"once_cell",
]
[[package]]
@ -416,12 +415,15 @@ dependencies = [
[[package]]
name = "dashmap"
version = "4.0.2"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"num_cpus",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
@ -461,10 +463,10 @@ dependencies = [
]
[[package]]
name = "env_logger"
version = "0.10.1"
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
@ -522,9 +524,9 @@ dependencies = [
[[package]]
name = "flutter_rust_bridge"
version = "2.10.0"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff1d2ad18166cead8c1b92b1c00e64aacc32e6ebd1ac95f77089c276c9c6bd8c"
checksum = "dde126295b2acc5f0a712e265e91b6fdc0ed38767496483e592ae7134db83725"
dependencies = [
"allo-isolate",
"android_logger",
@ -552,9 +554,9 @@ dependencies = [
[[package]]
name = "flutter_rust_bridge_macros"
version = "2.10.0"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36cf75fba54902e67db5eef4a520df1c9f604db6f71f106fbc012477e2d81542"
checksum = "d5f0420326b13675321b194928bb7830043b68cf8b810e1c651285c747abb080"
dependencies = [
"hex",
"md-5",
@ -1217,9 +1219,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oslog"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8343ce955f18e7e68c0207dd0ea776ec453035685395ababd2ea651c569728b3"
checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969"
dependencies = [
"cc",
"dashmap",

View file

@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "staticlib"]
[dependencies]
flutter_rust_bridge = { version = "=2.10.0", features = ["chrono"] }
flutter_rust_bridge = { version = "=2.11.1", features = ["chrono"] }
image = "0.25.6"
chrono = "0.4.41"
futures-util = "0.3.31"

View file

@ -1,5 +1,5 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
#![allow(
non_camel_case_types,
@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_opaque = RustOpaqueMoi,
default_rust_auto_opaque = RustAutoOpaqueMoi,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.10.0";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 885218533;
// Section: executor
@ -2441,7 +2441,7 @@ impl SseEncode for usize {
#[cfg(not(target_family = "wasm"))]
mod io {
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// Section: imports
@ -2507,7 +2507,7 @@ pub use io::*;
#[cfg(target_family = "wasm")]
mod web {
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.10.0.
// @generated by `flutter_rust_bridge`@ 2.11.1.
// Section: imports