separated download function

This commit is contained in:
Schnitzel5 2025-04-30 00:39:10 +02:00
parent 77b912d40e
commit cc5bb1cd2a
7 changed files with 408 additions and 18 deletions

View file

@ -478,7 +478,9 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
.findFirstSync();
if (entry == null || !entry.isDownload!) {
ref.watch(
downloadChapterProvider(chapter: chapter),
addDownloadToQueueProvider(
chapter: chapter,
),
);
}
} else {
@ -503,7 +505,7 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
.findFirstSync();
if (entry == null || !entry.isDownload!) {
ref.watch(
downloadChapterProvider(
addDownloadToQueueProvider(
chapter: chapter,
),
);
@ -527,7 +529,9 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
.findFirstSync();
if (entry == null || !entry.isDownload!) {
ref.watch(
downloadChapterProvider(chapter: chapter),
addDownloadToQueueProvider(
chapter: chapter,
),
);
}
}
@ -546,7 +550,9 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
.findFirstSync();
if (entry == null || !entry.isDownload!) {
ref.watch(
downloadChapterProvider(chapter: chapter),
addDownloadToQueueProvider(
chapter: chapter,
),
);
}
}
@ -998,7 +1004,9 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
if (entries.isEmpty ||
!entries.first.isDownload!) {
ref.watch(
downloadChapterProvider(chapter: chapter),
addDownloadToQueueProvider(
chapter: chapter,
),
);
}
}
@ -1540,16 +1548,26 @@ class _MangaDetailViewState extends ConsumerState<MangaDetailView>
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 {

View file

@ -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));
}
}
});

View file

@ -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<void> 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<void> 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<void> 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<void> 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<void> downloadChapter(
setProgress(progress);
});
}
if (callback != null) {
callback();
}
}
@riverpod
Future<void> 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;
});
}

View file

@ -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<AsyncValue<void>> {
/// 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<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'addDownloadToQueueProvider';
}
/// See also [addDownloadToQueue].
class AddDownloadToQueueProvider extends AutoDisposeFutureProvider<void> {
/// 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<void> 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<void> 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<void> {
/// The parameter `chapter` of this provider.
Chapter get chapter;
}
class _AddDownloadToQueueProviderElement
extends AutoDisposeFutureProviderElement<void> 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<AsyncValue<void>> {
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<AsyncValue<void>> {
return call(
chapter: provider.chapter,
useWifi: provider.useWifi,
callback: provider.callback,
);
}
@ -80,11 +214,13 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider<void> {
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<void> {
DownloadChapterFamily._allTransitiveDependencies,
chapter: chapter,
useWifi: useWifi,
callback: callback,
);
DownloadChapterProvider._internal(
@ -108,10 +245,12 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider<void> {
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<void> {
debugGetCreateSourceHash: null,
chapter: chapter,
useWifi: useWifi,
callback: callback,
),
);
}
@ -141,7 +281,8 @@ class DownloadChapterProvider extends AutoDisposeFutureProvider<void> {
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<void> {
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<void> {
/// 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<AsyncValue<void>> {
/// 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<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'processDownloadsProvider';
}
/// See also [processDownloads].
class ProcessDownloadsProvider extends AutoDisposeFutureProvider<void> {
/// 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<void> 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<void> 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<void> {
/// The parameter `useWifi` of this provider.
bool? get useWifi;
}
class _ProcessDownloadsProviderElement
extends AutoDisposeFutureProviderElement<void> 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

View file

@ -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;
}
}

View file

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

View file

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