mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
fix #91
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async' show Future, StreamController, scheduleMicrotask;
|
import 'dart:async' show Future, StreamController, scheduleMicrotask;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui show Codec;
|
import 'dart:ui' as ui show Codec;
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@@ -10,6 +11,39 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
extends ImageProvider<T> {
|
extends ImageProvider<T> {
|
||||||
const BaseImageProvider();
|
const BaseImageProvider();
|
||||||
|
|
||||||
|
static double? _effectiveScreenWidth;
|
||||||
|
|
||||||
|
static const double _normalComicImageRatio = 0.72;
|
||||||
|
|
||||||
|
static const double _minComicImageWidth = 1920 * _normalComicImageRatio;
|
||||||
|
|
||||||
|
static TargetImageSize _getTargetSize(width, height) {
|
||||||
|
if (_effectiveScreenWidth == null) {
|
||||||
|
final screens = PlatformDispatcher.instance.displays;
|
||||||
|
for (var screen in screens) {
|
||||||
|
if (screen.size.width > screen.size.height) {
|
||||||
|
_effectiveScreenWidth = max(
|
||||||
|
_effectiveScreenWidth ?? 0,
|
||||||
|
screen.size.height * _normalComicImageRatio,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_effectiveScreenWidth = max(
|
||||||
|
_effectiveScreenWidth ?? 0,
|
||||||
|
screen.size.width
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_effectiveScreenWidth! < _minComicImageWidth) {
|
||||||
|
_effectiveScreenWidth = _minComicImageWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (width > _effectiveScreenWidth!) {
|
||||||
|
height = (height * _effectiveScreenWidth! / width).round();
|
||||||
|
width = _effectiveScreenWidth!.round();
|
||||||
|
}
|
||||||
|
return TargetImageSize(width: width, height: height);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) {
|
ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) {
|
||||||
final chunkEvents = StreamController<ImageChunkEvent>();
|
final chunkEvents = StreamController<ImageChunkEvent>();
|
||||||
@@ -45,19 +79,12 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
|
|
||||||
while (data == null && !stop) {
|
while (data == null && !stop) {
|
||||||
try {
|
try {
|
||||||
if(_cache.containsKey(key.key)){
|
|
||||||
data = _cache[key.key];
|
|
||||||
} else {
|
|
||||||
data = await load(chunkEvents);
|
data = await load(chunkEvents);
|
||||||
_checkCacheSize();
|
|
||||||
_cache[key.key] = data;
|
|
||||||
_cacheSize += data.length;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(e.toString().contains("Invalid Status Code: 404")) {
|
if (e.toString().contains("Invalid Status Code: 404")) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
if(e.toString().contains("Invalid Status Code: 403")) {
|
if (e.toString().contains("Invalid Status Code: 403")) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
if (e.toString().contains("handshake")) {
|
if (e.toString().contains("handshake")) {
|
||||||
@@ -73,23 +100,24 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stop) {
|
if (stop) {
|
||||||
throw Exception("Image loading is stopped");
|
throw Exception("Image loading is stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data!.isEmpty) {
|
if (data!.isEmpty) {
|
||||||
throw Exception("Empty image data");
|
throw Exception("Empty image data");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final buffer = await ImmutableBuffer.fromUint8List(data);
|
final buffer = await ImmutableBuffer.fromUint8List(data);
|
||||||
return await decode(buffer);
|
return await decode(buffer, getTargetSize: _getTargetSize);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await CacheManager().delete(this.key);
|
await CacheManager().delete(this.key);
|
||||||
if (data.length < 2 * 1024) {
|
if (data.length < 2 * 1024) {
|
||||||
// data is too short, it's likely that the data is text, not image
|
// data is too short, it's likely that the data is text, not image
|
||||||
try {
|
try {
|
||||||
var text = const Utf8Codec(allowMalformed: false).decoder.convert(data);
|
var text =
|
||||||
|
const Utf8Codec(allowMalformed: false).decoder.convert(data);
|
||||||
throw Exception("Expected image data, but got text: $text");
|
throw Exception("Expected image data, but got text: $text");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
@@ -107,30 +135,6 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final _cache = <String, Uint8List>{};
|
|
||||||
|
|
||||||
static var _cacheSize = 0;
|
|
||||||
|
|
||||||
static var _cacheSizeLimit = 50 * 1024 * 1024;
|
|
||||||
|
|
||||||
static void _checkCacheSize(){
|
|
||||||
while (_cacheSize > _cacheSizeLimit){
|
|
||||||
var firstKey = _cache.keys.first;
|
|
||||||
_cacheSize -= _cache[firstKey]!.length;
|
|
||||||
_cache.remove(firstKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clearCache(){
|
|
||||||
_cache.clear();
|
|
||||||
_cacheSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setCacheSizeLimit(int size){
|
|
||||||
_cacheSizeLimit = size;
|
|
||||||
_checkCacheSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents);
|
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents);
|
||||||
|
|
||||||
String get key;
|
String get key;
|
||||||
|
@@ -2,6 +2,7 @@ import 'dart:async' show Future, StreamController;
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:venera/network/images.dart';
|
import 'package:venera/network/images.dart';
|
||||||
|
import 'package:venera/utils/io.dart';
|
||||||
import 'base_image_provider.dart';
|
import 'base_image_provider.dart';
|
||||||
import 'reader_image.dart' as image_provider;
|
import 'reader_image.dart' as image_provider;
|
||||||
|
|
||||||
@@ -20,6 +21,14 @@ class ReaderImageProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
|
||||||
|
if (imageKey.startsWith('file://')) {
|
||||||
|
var file = File(imageKey);
|
||||||
|
if (await file.exists()) {
|
||||||
|
return file.readAsBytes();
|
||||||
|
}
|
||||||
|
throw "Error: File not found.";
|
||||||
|
}
|
||||||
|
|
||||||
await for (var event
|
await for (var event
|
||||||
in ImageDownloader.loadComicImage(imageKey, sourceKey, cid, eid)) {
|
in ImageDownloader.loadComicImage(imageKey, sourceKey, cid, eid)) {
|
||||||
chunkEvents.add(ImageChunkEvent(
|
chunkEvents.add(ImageChunkEvent(
|
||||||
|
@@ -162,19 +162,22 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
} else {
|
} else {
|
||||||
int pageIndex = index - 1;
|
int pageIndex = index - 1;
|
||||||
int startIndex = pageIndex * reader.imagesPerPage;
|
int startIndex = pageIndex * reader.imagesPerPage;
|
||||||
int endIndex = math.min(startIndex + reader.imagesPerPage, reader.images!.length);
|
int endIndex = math.min(
|
||||||
List<String> pageImages = reader.images!.sublist(startIndex, endIndex);
|
startIndex + reader.imagesPerPage, reader.images!.length);
|
||||||
|
List<String> pageImages =
|
||||||
|
reader.images!.sublist(startIndex, endIndex);
|
||||||
|
|
||||||
cached[index] = true;
|
cached[index] = true;
|
||||||
cache(index);
|
cache(index);
|
||||||
|
|
||||||
photoViewControllers[index] = PhotoViewController();
|
photoViewControllers[index] = PhotoViewController();
|
||||||
|
|
||||||
if(reader.imagesPerPage == 1) {
|
if (reader.imagesPerPage == 1) {
|
||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
controller: photoViewControllers[index],
|
controller: photoViewControllers[index],
|
||||||
imageProvider: _createImageProviderFromKey(pageImages[0], context),
|
imageProvider:
|
||||||
|
_createImageProviderFromKey(pageImages[0], context),
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
errorBuilder: (_, error, s, retry) {
|
errorBuilder: (_, error, s, retry) {
|
||||||
return NetworkError(message: error.toString(), retry: retry);
|
return NetworkError(message: error.toString(), retry: retry);
|
||||||
@@ -645,9 +648,6 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
|
|
||||||
ImageProvider _createImageProviderFromKey(
|
ImageProvider _createImageProviderFromKey(
|
||||||
String imageKey, BuildContext context) {
|
String imageKey, BuildContext context) {
|
||||||
if (imageKey.startsWith('file://')) {
|
|
||||||
return FileImage(File(imageKey.replaceFirst("file://", '')));
|
|
||||||
} else {
|
|
||||||
var reader = context.reader;
|
var reader = context.reader;
|
||||||
return ReaderImageProvider(
|
return ReaderImageProvider(
|
||||||
imageKey,
|
imageKey,
|
||||||
@@ -655,22 +655,12 @@ ImageProvider _createImageProviderFromKey(
|
|||||||
reader.cid,
|
reader.cid,
|
||||||
reader.eid,
|
reader.eid,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageProvider _createImageProvider(int page, BuildContext context) {
|
ImageProvider _createImageProvider(int page, BuildContext context) {
|
||||||
var reader = context.reader;
|
var reader = context.reader;
|
||||||
var imageKey = reader.images![page - 1];
|
var imageKey = reader.images![page - 1];
|
||||||
if (imageKey.startsWith('file://')) {
|
return _createImageProviderFromKey(imageKey, context);
|
||||||
return FileImage(File(imageKey.replaceFirst("file://", '')));
|
|
||||||
} else {
|
|
||||||
return ReaderImageProvider(
|
|
||||||
imageKey,
|
|
||||||
reader.type.comicSource!.key,
|
|
||||||
reader.cid,
|
|
||||||
reader.eid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _precacheImage(int page, BuildContext context) {
|
void _precacheImage(int page, BuildContext context) {
|
||||||
|
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_memory_info/flutter_memory_info.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
@@ -21,6 +22,7 @@ import 'package:venera/foundation/comic_type.dart';
|
|||||||
import 'package:venera/foundation/history.dart';
|
import 'package:venera/foundation/history.dart';
|
||||||
import 'package:venera/foundation/image_provider/reader_image.dart';
|
import 'package:venera/foundation/image_provider/reader_image.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
|
import 'package:venera/foundation/log.dart';
|
||||||
import 'package:venera/pages/settings/settings_page.dart';
|
import 'package:venera/pages/settings/settings_page.dart';
|
||||||
import 'package:venera/utils/data_sync.dart';
|
import 'package:venera/utils/data_sync.dart';
|
||||||
import 'package:venera/utils/file_type.dart';
|
import 'package:venera/utils/file_type.dart';
|
||||||
@@ -142,9 +144,27 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
|
|||||||
if(appdata.settings['enableTurnPageByVolumeKey']) {
|
if(appdata.settings['enableTurnPageByVolumeKey']) {
|
||||||
handleVolumeEvent();
|
handleVolumeEvent();
|
||||||
}
|
}
|
||||||
|
setImageCacheSize();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setImageCacheSize() async {
|
||||||
|
var availableRAM = await MemoryInfo.getFreePhysicalMemorySize();
|
||||||
|
if (availableRAM == null) return;
|
||||||
|
int maxImageCacheSize;
|
||||||
|
if (availableRAM < 1 << 30) {
|
||||||
|
maxImageCacheSize = 100 << 20;
|
||||||
|
} else if (availableRAM < 2 << 30) {
|
||||||
|
maxImageCacheSize = 200 << 20;
|
||||||
|
} else if (availableRAM < 4 << 30) {
|
||||||
|
maxImageCacheSize = 300 << 20;
|
||||||
|
} else {
|
||||||
|
maxImageCacheSize = 500 << 20;
|
||||||
|
}
|
||||||
|
Log.info("Reader", "Detect available RAM: $availableRAM, set image cache size to $maxImageCacheSize");
|
||||||
|
PaintingBinding.instance.imageCache.maximumSizeBytes = maxImageCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
autoPageTurningTimer?.cancel();
|
autoPageTurningTimer?.cancel();
|
||||||
@@ -154,6 +174,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
|
|||||||
Future.microtask(() {
|
Future.microtask(() {
|
||||||
DataSync().onDataChanged();
|
DataSync().onDataChanged();
|
||||||
});
|
});
|
||||||
|
PaintingBinding.instance.imageCache.maximumSizeBytes = 100 << 20;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
pubspec.lock
13
pubspec.lock
@@ -388,6 +388,15 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_memory_info:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: HEAD
|
||||||
|
resolved-ref: a401e3f96dca6ab0ab07b6b2ec0b649833f04c14
|
||||||
|
url: "https://github.com/wgh136/flutter_memory_info"
|
||||||
|
source: git
|
||||||
|
version: "0.0.1"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1070,10 +1079,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
|
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.5.4"
|
version: "5.9.0"
|
||||||
window_manager:
|
window_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@@ -72,6 +72,9 @@ dependencies:
|
|||||||
pdf: ^3.11.1
|
pdf: ^3.11.1
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
shimmer: ^3.0.0
|
shimmer: ^3.0.0
|
||||||
|
flutter_memory_info:
|
||||||
|
git:
|
||||||
|
url: https://github.com/wgh136/flutter_memory_info
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user