mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Improve image loading
This commit is contained in:
@@ -78,7 +78,13 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
||||
|
||||
while (data == null && !stop) {
|
||||
try {
|
||||
data = await load(chunkEvents);
|
||||
data = await load(chunkEvents, () {
|
||||
if (stop) {
|
||||
throw const _ImageLoadingStopException();
|
||||
}
|
||||
});
|
||||
} on _ImageLoadingStopException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
if (e.toString().contains("Invalid Status Code: 404")) {
|
||||
rethrow;
|
||||
@@ -100,7 +106,7 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
throw Exception("Image loading is stopped");
|
||||
throw const _ImageLoadingStopException();
|
||||
}
|
||||
|
||||
if (data!.isEmpty) {
|
||||
@@ -127,6 +133,8 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
} on _ImageLoadingStopException {
|
||||
rethrow;
|
||||
} catch (e, s) {
|
||||
scheduleMicrotask(() {
|
||||
PaintingBinding.instance.imageCache.evict(key);
|
||||
@@ -138,7 +146,10 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents);
|
||||
Future<Uint8List> load(
|
||||
StreamController<ImageChunkEvent> chunkEvents,
|
||||
void Function() checkStop,
|
||||
);
|
||||
|
||||
String get key;
|
||||
|
||||
@@ -159,3 +170,7 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
||||
}
|
||||
|
||||
typedef FileDecoderCallback = Future<ui.Codec> Function(Uint8List);
|
||||
|
||||
class _ImageLoadingStopException implements Exception {
|
||||
const _ImageLoadingStopException();
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import 'dart:async' show Future, StreamController;
|
||||
import 'dart:async' show Future;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:venera/network/images.dart';
|
||||
@@ -26,9 +26,10 @@ class CachedImageProvider
|
||||
static const _kMaxLoadingCount = 8;
|
||||
|
||||
@override
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||
Future<Uint8List> load(chunkEvents, checkStop) async {
|
||||
while(loadingCount > _kMaxLoadingCount) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
checkStop();
|
||||
}
|
||||
loadingCount++;
|
||||
try {
|
||||
@@ -37,6 +38,7 @@ class CachedImageProvider
|
||||
return file.readAsBytes();
|
||||
}
|
||||
await for (var progress in ImageDownloader.loadThumbnail(url, sourceKey, cid)) {
|
||||
checkStop();
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: progress.currentBytes,
|
||||
expectedTotalBytes: progress.totalBytes,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import 'dart:async' show Future, StreamController;
|
||||
import 'dart:async' show Future;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
@@ -17,7 +17,7 @@ class HistoryImageProvider
|
||||
final History history;
|
||||
|
||||
@override
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||
Future<Uint8List> load(chunkEvents, checkStop) async {
|
||||
var url = history.cover;
|
||||
if (!url.contains('/')) {
|
||||
var localComic = LocalManager().find(history.id, history.type);
|
||||
@@ -27,6 +27,7 @@ class HistoryImageProvider
|
||||
var comicSource =
|
||||
history.type.comicSource ?? (throw "Comic source not found.");
|
||||
var comic = await comicSource.loadComicInfo!(history.id);
|
||||
checkStop();
|
||||
url = comic.data.cover;
|
||||
history.cover = url;
|
||||
HistoryManager().addHistory(history);
|
||||
@@ -36,6 +37,7 @@ class HistoryImageProvider
|
||||
history.type.sourceKey,
|
||||
history.id,
|
||||
)) {
|
||||
checkStop();
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: progress.currentBytes,
|
||||
expectedTotalBytes: progress.totalBytes,
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import 'dart:async' show Future, StreamController;
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -29,30 +28,36 @@ class ImageFavoritesProvider
|
||||
String get eid => imageFavorite.eid;
|
||||
|
||||
@override
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent>? chunkEvents) async {
|
||||
Future<Uint8List> load(
|
||||
StreamController<ImageChunkEvent>? chunkEvents,
|
||||
void Function()? checkStop,
|
||||
) async {
|
||||
var imageKey = imageFavorite.imageKey;
|
||||
var localImage = await getImageFromLocal();
|
||||
checkStop?.call();
|
||||
if (localImage != null) {
|
||||
return localImage;
|
||||
}
|
||||
var cacheImage = await readFromCache();
|
||||
checkStop?.call();
|
||||
if (cacheImage != null) {
|
||||
return cacheImage;
|
||||
}
|
||||
var gotImageKey = false;
|
||||
if (imageKey == "") {
|
||||
imageKey = await getImageKey();
|
||||
checkStop?.call();
|
||||
gotImageKey = true;
|
||||
}
|
||||
Uint8List image;
|
||||
try {
|
||||
image = await getImageFromNetwork(imageKey, chunkEvents);
|
||||
image = await getImageFromNetwork(imageKey, chunkEvents, checkStop);
|
||||
} catch (e) {
|
||||
if (gotImageKey) {
|
||||
rethrow;
|
||||
} else {
|
||||
imageKey = await getImageKey();
|
||||
image = await getImageFromNetwork(imageKey, chunkEvents);
|
||||
image = await getImageFromNetwork(imageKey, chunkEvents, checkStop);
|
||||
}
|
||||
}
|
||||
await writeToCache(image);
|
||||
@@ -106,9 +111,13 @@ class ImageFavoritesProvider
|
||||
}
|
||||
|
||||
Future<Uint8List> getImageFromNetwork(
|
||||
String imageKey, StreamController<ImageChunkEvent>? chunkEvents) async {
|
||||
String imageKey,
|
||||
StreamController<ImageChunkEvent>? chunkEvents,
|
||||
void Function()? checkStop,
|
||||
) async {
|
||||
await for (var progress
|
||||
in ImageDownloader.loadComicImage(imageKey, sourceKey, cid, eid)) {
|
||||
checkStop?.call();
|
||||
if (chunkEvents != null) {
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: progress.currentBytes,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import 'dart:async' show Future, StreamController;
|
||||
import 'dart:async' show Future;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
@@ -16,7 +16,7 @@ class LocalComicImageProvider
|
||||
final LocalComic comic;
|
||||
|
||||
@override
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||
Future<Uint8List> load(chunkEvents, checkStop) async {
|
||||
File? file = comic.coverFile;
|
||||
if(! await file.exists()) {
|
||||
file = null;
|
||||
@@ -49,6 +49,7 @@ class LocalComicImageProvider
|
||||
if(file == null) {
|
||||
throw "Error: Cover not found.";
|
||||
}
|
||||
checkStop();
|
||||
var data = await file.readAsBytes();
|
||||
if(data.isEmpty) {
|
||||
throw "Exception: Empty file(${file.path}).";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import 'dart:async' show Future, StreamController;
|
||||
import 'dart:async' show Future;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:venera/foundation/app.dart';
|
||||
@@ -28,7 +28,7 @@ class LocalFavoriteImageProvider
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||
Future<Uint8List> load(chunkEvents, checkStop) async {
|
||||
var sourceKey = ComicSource.fromIntKey(intKey)?.key;
|
||||
var fileName = key.hashCode.toString();
|
||||
var file = File(FilePath.join(App.dataPath, 'favorite_cover', fileName));
|
||||
@@ -37,7 +37,9 @@ class LocalFavoriteImageProvider
|
||||
} else {
|
||||
await file.create(recursive: true);
|
||||
}
|
||||
checkStop();
|
||||
await for (var progress in ImageDownloader.loadThumbnail(url, sourceKey)) {
|
||||
checkStop();
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: progress.currentBytes,
|
||||
expectedTotalBytes: progress.totalBytes,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import 'dart:async' show Future, StreamController;
|
||||
import 'dart:async' show Future;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_qjs/flutter_qjs.dart';
|
||||
@@ -12,7 +12,7 @@ import 'package:venera/foundation/appdata.dart';
|
||||
class ReaderImageProvider
|
||||
extends BaseImageProvider<image_provider.ReaderImageProvider> {
|
||||
/// Image provider for normal image.
|
||||
const ReaderImageProvider(this.imageKey, this.sourceKey, this.cid, this.eid);
|
||||
const ReaderImageProvider(this.imageKey, this.sourceKey, this.cid, this.eid, this.page);
|
||||
|
||||
final String imageKey;
|
||||
|
||||
@@ -22,8 +22,10 @@ class ReaderImageProvider
|
||||
|
||||
final String eid;
|
||||
|
||||
final int page;
|
||||
|
||||
@override
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||
Future<Uint8List> load(chunkEvents, checkStop) async {
|
||||
Uint8List? imageBytes;
|
||||
if (imageKey.startsWith('file://')) {
|
||||
var file = File(imageKey);
|
||||
@@ -35,6 +37,7 @@ class ReaderImageProvider
|
||||
} else {
|
||||
await for (var event
|
||||
in ImageDownloader.loadComicImage(imageKey, sourceKey, cid, eid)) {
|
||||
checkStop();
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: event.currentBytes,
|
||||
expectedTotalBytes: event.totalBytes,
|
||||
@@ -60,14 +63,57 @@ class ReaderImageProvider
|
||||
})()
|
||||
''');
|
||||
if (func is JSInvokable) {
|
||||
var result = await func.invoke([imageBytes, cid, eid]);
|
||||
func.free();
|
||||
var result = func.invoke([imageBytes, cid, eid, page, sourceKey]);
|
||||
if (result is Uint8List) {
|
||||
return result;
|
||||
imageBytes = result;
|
||||
} else if (result is Future) {
|
||||
var futureResult = await result;
|
||||
if (futureResult is Uint8List) {
|
||||
imageBytes = futureResult;
|
||||
}
|
||||
} else if (result is Map) {
|
||||
var image = result['image'];
|
||||
if (image is Uint8List) {
|
||||
imageBytes = image;
|
||||
} else if (image is Future) {
|
||||
JSInvokable? onCancel;
|
||||
if (result['onCancel'] is JSInvokable) {
|
||||
onCancel = result['onCancel'];
|
||||
}
|
||||
if (onCancel == null) {
|
||||
var futureImage = await image;
|
||||
if (futureImage is Uint8List) {
|
||||
imageBytes = futureImage;
|
||||
}
|
||||
} else {
|
||||
dynamic futureImage;
|
||||
image.then((value) {
|
||||
futureImage = value;
|
||||
futureImage ??= Uint8List(0);
|
||||
});
|
||||
while (futureImage == null) {
|
||||
try {
|
||||
checkStop();
|
||||
}
|
||||
catch(e) {
|
||||
onCancel.invoke([]);
|
||||
onCancel.free();
|
||||
func.free();
|
||||
rethrow;
|
||||
}
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
}
|
||||
if (futureImage is Uint8List) {
|
||||
imageBytes = futureImage;
|
||||
}
|
||||
}
|
||||
onCancel?.free();
|
||||
}
|
||||
}
|
||||
func.free();
|
||||
}
|
||||
}
|
||||
return imageBytes;
|
||||
return imageBytes!;
|
||||
}
|
||||
|
||||
@override
|
||||
|
Reference in New Issue
Block a user