From cc5bb1cd2a0619658c1dbe2872f3e8581e1440a4 Mon Sep 17 00:00:00 2001 From: Schnitzel5 Date: Wed, 30 Apr 2025 00:39:10 +0200 Subject: [PATCH] separated download function --- .../manga/detail/manga_detail_view.dart | 36 ++- .../detail/providers/state_providers.dart | 2 +- .../download/providers/download_provider.dart | 67 ++++- .../providers/download_provider.g.dart | 281 +++++++++++++++++- .../download_queue/download_queue_screen.dart | 36 +++ lib/services/aniskip.g.dart | 2 +- lib/services/trackers/anilist.g.dart | 2 +- 7 files changed, 408 insertions(+), 18 deletions(-) diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 10a1c746..10799665 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -478,7 +478,9 @@ class _MangaDetailViewState extends ConsumerState .findFirstSync(); if (entry == null || !entry.isDownload!) { ref.watch( - downloadChapterProvider(chapter: chapter), + addDownloadToQueueProvider( + chapter: chapter, + ), ); } } else { @@ -503,7 +505,7 @@ class _MangaDetailViewState extends ConsumerState .findFirstSync(); if (entry == null || !entry.isDownload!) { ref.watch( - downloadChapterProvider( + addDownloadToQueueProvider( chapter: chapter, ), ); @@ -527,7 +529,9 @@ class _MangaDetailViewState extends ConsumerState .findFirstSync(); if (entry == null || !entry.isDownload!) { ref.watch( - downloadChapterProvider(chapter: chapter), + addDownloadToQueueProvider( + chapter: chapter, + ), ); } } @@ -546,7 +550,9 @@ class _MangaDetailViewState extends ConsumerState .findFirstSync(); if (entry == null || !entry.isDownload!) { ref.watch( - downloadChapterProvider(chapter: chapter), + addDownloadToQueueProvider( + chapter: chapter, + ), ); } } @@ -998,7 +1004,9 @@ class _MangaDetailViewState extends ConsumerState if (entries.isEmpty || !entries.first.isDownload!) { ref.watch( - downloadChapterProvider(chapter: chapter), + addDownloadToQueueProvider( + chapter: chapter, + ), ); } } @@ -1540,16 +1548,26 @@ class _MangaDetailViewState extends ConsumerState return; } if (value == 0) { - final genre = widget.manga!.genre![i]; + final genre = + widget.manga!.genre![i]; switch (widget.manga!.itemType) { case ItemType.manga: - context.pushReplacement('/MangaLibrary', extra: genre); + context.pushReplacement( + '/MangaLibrary', + extra: genre, + ); break; case ItemType.anime: - context.pushReplacement('/AnimeLibrary', extra: genre); + context.pushReplacement( + '/AnimeLibrary', + extra: genre, + ); break; case ItemType.novel: - context.pushReplacement('/NovelLibrary', extra: genre); + context.pushReplacement( + '/NovelLibrary', + extra: genre, + ); break; } } else { diff --git a/lib/modules/manga/detail/providers/state_providers.dart b/lib/modules/manga/detail/providers/state_providers.dart index 46bdd54a..64c05e28 100644 --- a/lib/modules/manga/detail/providers/state_providers.dart +++ b/lib/modules/manga/detail/providers/state_providers.dart @@ -364,7 +364,7 @@ class ChapterSetDownloadState extends _$ChapterSetDownloadState { final entries = isar.downloads.filter().idEqualTo(chapter.id).findAllSync(); if (entries.isEmpty || !entries.first.isDownload!) { - ref.watch(downloadChapterProvider(chapter: chapter)); + ref.watch(addDownloadToQueueProvider(chapter: chapter)); } } }); diff --git a/lib/modules/manga/download/providers/download_provider.dart b/lib/modules/manga/download/providers/download_provider.dart index 1b97f5a4..ab73c3b9 100644 --- a/lib/modules/manga/download/providers/download_provider.dart +++ b/lib/modules/manga/download/providers/download_provider.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:ui'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:isar/isar.dart'; import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/page.dart'; @@ -27,20 +29,40 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; part 'download_provider.g.dart'; +@riverpod +Future addDownloadToQueue(Ref ref, {required Chapter chapter}) async { + final download = isar.downloads.getSync(chapter.id!); + if (download == null) { + final download = Download( + id: chapter.id, + succeeded: 0, + failed: 0, + total: 100, + isDownload: false, + isStartDownload: true, + ); + isar.writeTxnSync(() { + isar.downloads.putSync(download..chapter.value = chapter); + }); + } +} + @riverpod Future downloadChapter( Ref ref, { required Chapter chapter, bool? useWifi, + VoidCallback? callback, }) async { bool onlyOnWifi = useWifi ?? ref.watch(onlyOnWifiStateProvider); final connectivity = await Connectivity().checkConnectivity(); - final isOnWifi = connectivity.contains(ConnectivityResult.wifi) || connectivity.contains(ConnectivityResult.ethernet); + final isOnWifi = + connectivity.contains(ConnectivityResult.wifi) || + connectivity.contains(ConnectivityResult.ethernet); if (onlyOnWifi && !isOnWifi) { botToast(navigatorKey.currentContext!.l10n.downloads_are_limited_to_wifi); return; } - final concurrentDownloads = ref.watch(concurrentDownloadsStateProvider); final http = MClient.init( reqcopyWith: {'useDartHttpClient': true, 'followRedirects': false}, ); @@ -184,7 +206,6 @@ Future downloadChapter( headers: videosUrls.first.headers ?? {}, fileName: p.join(mangaMainDirectory!.path, "$chapterName.mp4"), chapter: chapter, - concurrentDownloads: concurrentDownloads, ); } else { pageUrls = [PageUrl(videosUrls.first.url)]; @@ -328,7 +349,7 @@ Future downloadChapter( }); } else { savePageUrls(); - await MDownloader(chapter: chapter, pageUrls: pages, concurrentDownloads: concurrentDownloads).download((progress) { + await MDownloader(chapter: chapter, pageUrls: pages).download((progress) { setProgress(progress); }); } @@ -337,4 +358,42 @@ Future downloadChapter( setProgress(progress); }); } + if (callback != null) { + callback(); + } +} + +@riverpod +Future processDownloads(Ref ref, {bool? useWifi}) async { + final ongoingDownloads = + await isar.downloads + .filter() + .idIsNotNull() + .isDownloadEqualTo(false) + .isStartDownloadEqualTo(true) + .findAll(); + final maxConcurrentDownloads = ref.read(concurrentDownloadsStateProvider); + int index = 0; + int downloaded = 0; + int current = 0; + await Future.doWhile(() async { + await Future.delayed(const Duration(seconds: 1)); + if (ongoingDownloads.length == downloaded) { + return false; + } + if (current < maxConcurrentDownloads) { + current++; + ref.read( + downloadChapterProvider( + chapter: ongoingDownloads[index++].chapter.value!, + useWifi: useWifi, + callback: () { + downloaded++; + current--; + }, + ), + ); + } + return true; + }); } diff --git a/lib/modules/manga/download/providers/download_provider.g.dart b/lib/modules/manga/download/providers/download_provider.g.dart index 73e6cb76..5678edb8 100644 --- a/lib/modules/manga/download/providers/download_provider.g.dart +++ b/lib/modules/manga/download/providers/download_provider.g.dart @@ -6,7 +6,8 @@ part of 'download_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$downloadChapterHash() => r'ab708c05c18d3449efbb8064f3488adb0e68b1f2'; +String _$addDownloadToQueueHash() => + r'35e8e724755be265a9bf167e4641336630a465d2'; /// Copied from Dart SDK class _SystemHash { @@ -29,6 +30,136 @@ class _SystemHash { } } +/// See also [addDownloadToQueue]. +@ProviderFor(addDownloadToQueue) +const addDownloadToQueueProvider = AddDownloadToQueueFamily(); + +/// See also [addDownloadToQueue]. +class AddDownloadToQueueFamily extends Family> { + /// See also [addDownloadToQueue]. + const AddDownloadToQueueFamily(); + + /// See also [addDownloadToQueue]. + AddDownloadToQueueProvider call({ + required Chapter chapter, + }) { + return AddDownloadToQueueProvider( + chapter: chapter, + ); + } + + @override + AddDownloadToQueueProvider getProviderOverride( + covariant AddDownloadToQueueProvider provider, + ) { + return call( + chapter: provider.chapter, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'addDownloadToQueueProvider'; +} + +/// See also [addDownloadToQueue]. +class AddDownloadToQueueProvider extends AutoDisposeFutureProvider { + /// See also [addDownloadToQueue]. + AddDownloadToQueueProvider({ + required Chapter chapter, + }) : this._internal( + (ref) => addDownloadToQueue( + ref as AddDownloadToQueueRef, + chapter: chapter, + ), + from: addDownloadToQueueProvider, + name: r'addDownloadToQueueProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$addDownloadToQueueHash, + dependencies: AddDownloadToQueueFamily._dependencies, + allTransitiveDependencies: + AddDownloadToQueueFamily._allTransitiveDependencies, + chapter: chapter, + ); + + AddDownloadToQueueProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.chapter, + }) : super.internal(); + + final Chapter chapter; + + @override + Override overrideWith( + FutureOr Function(AddDownloadToQueueRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: AddDownloadToQueueProvider._internal( + (ref) => create(ref as AddDownloadToQueueRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + chapter: chapter, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _AddDownloadToQueueProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is AddDownloadToQueueProvider && other.chapter == chapter; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, chapter.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin AddDownloadToQueueRef on AutoDisposeFutureProviderRef { + /// The parameter `chapter` of this provider. + Chapter get chapter; +} + +class _AddDownloadToQueueProviderElement + extends AutoDisposeFutureProviderElement with AddDownloadToQueueRef { + _AddDownloadToQueueProviderElement(super.provider); + + @override + Chapter get chapter => (origin as AddDownloadToQueueProvider).chapter; +} + +String _$downloadChapterHash() => r'd20136bb2b86f930c3c48bc0dd07b16eaf1de133'; + /// See also [downloadChapter]. @ProviderFor(downloadChapter) const downloadChapterProvider = DownloadChapterFamily(); @@ -42,10 +173,12 @@ class DownloadChapterFamily extends Family> { DownloadChapterProvider call({ required Chapter chapter, bool? useWifi, + void Function()? callback, }) { return DownloadChapterProvider( chapter: chapter, useWifi: useWifi, + callback: callback, ); } @@ -56,6 +189,7 @@ class DownloadChapterFamily extends Family> { return call( chapter: provider.chapter, useWifi: provider.useWifi, + callback: provider.callback, ); } @@ -80,11 +214,13 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider { DownloadChapterProvider({ required Chapter chapter, bool? useWifi, + void Function()? callback, }) : this._internal( (ref) => downloadChapter( ref as DownloadChapterRef, chapter: chapter, useWifi: useWifi, + callback: callback, ), from: downloadChapterProvider, name: r'downloadChapterProvider', @@ -97,6 +233,7 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider { DownloadChapterFamily._allTransitiveDependencies, chapter: chapter, useWifi: useWifi, + callback: callback, ); DownloadChapterProvider._internal( @@ -108,10 +245,12 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider { required super.from, required this.chapter, required this.useWifi, + required this.callback, }) : super.internal(); final Chapter chapter; final bool? useWifi; + final void Function()? callback; @override Override overrideWith( @@ -128,6 +267,7 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider { debugGetCreateSourceHash: null, chapter: chapter, useWifi: useWifi, + callback: callback, ), ); } @@ -141,7 +281,8 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider { bool operator ==(Object other) { return other is DownloadChapterProvider && other.chapter == chapter && - other.useWifi == useWifi; + other.useWifi == useWifi && + other.callback == callback; } @override @@ -149,6 +290,7 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider { var hash = _SystemHash.combine(0, runtimeType.hashCode); hash = _SystemHash.combine(hash, chapter.hashCode); hash = _SystemHash.combine(hash, useWifi.hashCode); + hash = _SystemHash.combine(hash, callback.hashCode); return _SystemHash.finish(hash); } @@ -162,6 +304,9 @@ mixin DownloadChapterRef on AutoDisposeFutureProviderRef { /// The parameter `useWifi` of this provider. bool? get useWifi; + + /// The parameter `callback` of this provider. + void Function()? get callback; } class _DownloadChapterProviderElement @@ -172,6 +317,138 @@ class _DownloadChapterProviderElement Chapter get chapter => (origin as DownloadChapterProvider).chapter; @override bool? get useWifi => (origin as DownloadChapterProvider).useWifi; + @override + void Function()? get callback => (origin as DownloadChapterProvider).callback; +} + +String _$processDownloadsHash() => r'6204b2ae0394c2b422ab3b5cd2eaaaa822a59ee1'; + +/// See also [processDownloads]. +@ProviderFor(processDownloads) +const processDownloadsProvider = ProcessDownloadsFamily(); + +/// See also [processDownloads]. +class ProcessDownloadsFamily extends Family> { + /// See also [processDownloads]. + const ProcessDownloadsFamily(); + + /// See also [processDownloads]. + ProcessDownloadsProvider call({ + bool? useWifi, + }) { + return ProcessDownloadsProvider( + useWifi: useWifi, + ); + } + + @override + ProcessDownloadsProvider getProviderOverride( + covariant ProcessDownloadsProvider provider, + ) { + return call( + useWifi: provider.useWifi, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'processDownloadsProvider'; +} + +/// See also [processDownloads]. +class ProcessDownloadsProvider extends AutoDisposeFutureProvider { + /// See also [processDownloads]. + ProcessDownloadsProvider({ + bool? useWifi, + }) : this._internal( + (ref) => processDownloads( + ref as ProcessDownloadsRef, + useWifi: useWifi, + ), + from: processDownloadsProvider, + name: r'processDownloadsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$processDownloadsHash, + dependencies: ProcessDownloadsFamily._dependencies, + allTransitiveDependencies: + ProcessDownloadsFamily._allTransitiveDependencies, + useWifi: useWifi, + ); + + ProcessDownloadsProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.useWifi, + }) : super.internal(); + + final bool? useWifi; + + @override + Override overrideWith( + FutureOr Function(ProcessDownloadsRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: ProcessDownloadsProvider._internal( + (ref) => create(ref as ProcessDownloadsRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + useWifi: useWifi, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _ProcessDownloadsProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ProcessDownloadsProvider && other.useWifi == useWifi; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, useWifi.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin ProcessDownloadsRef on AutoDisposeFutureProviderRef { + /// The parameter `useWifi` of this provider. + bool? get useWifi; +} + +class _ProcessDownloadsProviderElement + extends AutoDisposeFutureProviderElement with ProcessDownloadsRef { + _ProcessDownloadsProviderElement(super.provider); + + @override + bool? get useWifi => (origin as ProcessDownloadsProvider).useWifi; } // 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 diff --git a/lib/modules/more/download_queue/download_queue_screen.dart b/lib/modules/more/download_queue/download_queue_screen.dart index bcd0eca7..e08f2b0c 100644 --- a/lib/modules/more/download_queue/download_queue_screen.dart +++ b/lib/modules/more/download_queue/download_queue_screen.dart @@ -5,6 +5,8 @@ import 'package:isar/isar.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/download.dart'; +import 'package:mangayomi/modules/manga/detail/widgets/custom_floating_action_btn.dart'; +import 'package:mangayomi/modules/manga/download/providers/download_provider.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; import 'package:mangayomi/utils/extensions/chapter.dart'; import 'package:mangayomi/utils/global_style.dart'; @@ -172,6 +174,23 @@ class DownloadQueueScreen extends ConsumerWidget { ), order: GroupedListOrder.DESC, ), + floatingActionButton: CustomFloatingActionBtn( + isExtended: false, + label: l10n.download_queue, + onPressed: () { + ref.read(processDownloadsProvider()); + }, + textWidth: + measureText( + l10n.download_queue, + Theme.of(context).textTheme.labelLarge!, + ).width, + width: calculateDynamicButtonWidth( + l10n.download_queue, + Theme.of(context).textTheme.labelLarge!, + 50, + ), // 50 Padding, else RenderFlex overflow Exception + ), ); } return Scaffold( @@ -181,4 +200,21 @@ class DownloadQueueScreen extends ConsumerWidget { }, ); } + + Size measureText(String text, TextStyle style) { + final TextPainter textPainter = TextPainter( + text: TextSpan(text: text, style: style), + textDirection: TextDirection.ltr, + )..layout(); + return textPainter.size; + } + + double calculateDynamicButtonWidth( + String text, + TextStyle textStyle, + double padding, + ) { + final textSize = measureText(text, textStyle); + return textSize.width + padding; + } } diff --git a/lib/services/aniskip.g.dart b/lib/services/aniskip.g.dart index b2e1defb..c05db920 100644 --- a/lib/services/aniskip.g.dart +++ b/lib/services/aniskip.g.dart @@ -6,7 +6,7 @@ part of 'aniskip.dart'; // RiverpodGenerator // ************************************************************************** -String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421'; +String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c'; /// See also [AniSkip]. @ProviderFor(AniSkip) diff --git a/lib/services/trackers/anilist.g.dart b/lib/services/trackers/anilist.g.dart index 558da2c4..834afd64 100644 --- a/lib/services/trackers/anilist.g.dart +++ b/lib/services/trackers/anilist.g.dart @@ -6,7 +6,7 @@ part of 'anilist.dart'; // RiverpodGenerator // ************************************************************************** -String _$anilistHash() => r'70e8cd537270a9054a1ef72de117fc7ad5545218'; +String _$anilistHash() => r'ddd07acc8d28d2aa95c942566109e9393ca9e5ed'; /// Copied from Dart SDK class _SystemHash {