This commit is contained in:
2024-12-11 13:41:34 +08:00
parent da025b16ff
commit d7b91f6a50
6 changed files with 101 additions and 65 deletions

View File

@@ -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,14 +79,7 @@ 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;
@@ -83,13 +110,14 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
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;

View File

@@ -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(

View File

@@ -162,8 +162,10 @@ 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);
@@ -174,7 +176,8 @@ class _GalleryModeState extends State<_GalleryMode>
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,
@@ -656,21 +656,11 @@ ImageProvider _createImageProviderFromKey(
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) {

View File

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

View File

@@ -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:

View File

@@ -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: