Improve comic image loading retry

This commit is contained in:
2025-02-14 10:46:49 +08:00
parent ef2e621da2
commit d179b39b64
4 changed files with 97 additions and 60 deletions

View File

@@ -9,7 +9,7 @@ abstract class GlobalState {
static T find<T extends State>([Object? key]) { static T find<T extends State>([Object? key]) {
for (var pair in _state) { for (var pair in _state) {
if (pair.left == key && pair.right is T) { if ((key == null || pair.left == key) && pair.right is T) {
return pair.right as T; return pair.right as T;
} }
} }
@@ -18,7 +18,7 @@ abstract class GlobalState {
static T? findOrNull<T extends State>([Object? key]) { static T? findOrNull<T extends State>([Object? key]) {
for (var pair in _state) { for (var pair in _state) {
if (pair.left == key && pair.right is T) { if ((key == null || pair.left == key) && pair.right is T) {
return pair.right as T; return pair.right as T;
} }
} }

View File

@@ -24,8 +24,7 @@ class ComicImage extends StatefulWidget {
Map<String, String>? headers, Map<String, String>? headers,
int? cacheWidth, int? cacheWidth,
int? cacheHeight, int? cacheHeight,
} }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, image),
): image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, image),
assert(cacheWidth == null || cacheWidth > 0), assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0); assert(cacheHeight == null || cacheHeight > 0);
@@ -138,8 +137,8 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
} }
void _updateInvertColors() { void _updateInvertColors() {
_invertColors = MediaQuery.maybeInvertColorsOf(context) _invertColors = MediaQuery.maybeInvertColorsOf(context) ??
?? SemanticsBinding.instance.accessibilityFeatures.invertColors; SemanticsBinding.instance.accessibilityFeatures.invertColors;
} }
void _resolveImage() { void _resolveImage() {
@@ -150,14 +149,17 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
final ImageStream newStream = final ImageStream newStream =
provider.resolve(createLocalImageConfiguration( provider.resolve(createLocalImageConfiguration(
context, context,
size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null, size: widget.width != null && widget.height != null
? Size(widget.width!, widget.height!)
: null,
)); ));
_updateSourceStream(newStream); _updateSourceStream(newStream);
} }
ImageStreamListener? _imageStreamListener; ImageStreamListener? _imageStreamListener;
ImageStreamListener _getListener({bool recreateListener = false}) { ImageStreamListener _getListener({bool recreateListener = false}) {
if(_imageStreamListener == null || recreateListener) { if (_imageStreamListener == null || recreateListener) {
_lastException = null; _lastException = null;
_imageStreamListener = ImageStreamListener( _imageStreamListener = ImageStreamListener(
_handleImageFrame, _handleImageFrame,
@@ -191,7 +193,8 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
void _replaceImage({required ImageInfo? info}) { void _replaceImage({required ImageInfo? info}) {
final ImageInfo? oldImageInfo = _imageInfo; final ImageInfo? oldImageInfo = _imageInfo;
SchedulerBinding.instance.addPostFrameCallback((_) => oldImageInfo?.dispose()); SchedulerBinding.instance
.addPostFrameCallback((_) => oldImageInfo?.dispose());
_imageInfo = info; _imageInfo = info;
} }
@@ -208,7 +211,9 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
} }
if (!widget.gaplessPlayback) { if (!widget.gaplessPlayback) {
setState(() { _replaceImage(info: null); }); setState(() {
_replaceImage(info: null);
});
} }
setState(() { setState(() {
@@ -247,7 +252,9 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
return; return;
} }
if (keepStreamAlive && _completerHandle == null && _imageStream?.completer != null) { if (keepStreamAlive &&
_completerHandle == null &&
_imageStream?.completer != null) {
_completerHandle = _imageStream!.completer!.keepAlive(); _completerHandle = _imageStream!.completer!.keepAlive();
} }
@@ -269,26 +276,41 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
children: [ children: [
Expanded( Expanded(
child: Center( child: Center(
child: Text(_lastException.toString(), maxLines: 3,), child: Text(
_lastException.toString(),
maxLines: 3,
), ),
), ),
const SizedBox(height: 4,), ),
const SizedBox(
height: 4,
),
MouseRegion( MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: Listener( child: Listener(
onPointerDown: (details){ onPointerDown: (details) {
GlobalState.find<_ReaderGestureDetectorState>().ignoreNextTap();
setState(() {
_loadingProgress = null;
_lastException = null;
});
_resolveImage(); _resolveImage();
}, },
child: const SizedBox( child: SizedBox(
width: 84, width: 84,
height: 36, height: 36,
child: Center( child: Center(
child: Text("Retry", style: TextStyle(color: Colors.blue),), child: Text(
"Retry".tl,
style: TextStyle(color: Colors.blue),
), ),
), ),
), ),
), ),
const SizedBox(height: 16,), ),
const SizedBox(
height: 16,
),
], ],
), ),
), ),
@@ -300,34 +322,32 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
var width = widget.width; var width = widget.width;
var height = widget.height; var height = widget.height;
if(_imageInfo != null) { if (_imageInfo != null) {
// Record the height and the width of the image // Record the height and the width of the image
_cache[widget.image.hashCode] = Size( _cache[widget.image.hashCode] = Size(_imageInfo!.image.width.toDouble(),
_imageInfo!.image.width.toDouble(), _imageInfo!.image.height.toDouble());
_imageInfo!.image.height.toDouble()
);
} }
Size? cacheSize = _cache[widget.image.hashCode]; Size? cacheSize = _cache[widget.image.hashCode];
if(cacheSize != null){ if (cacheSize != null) {
if(width == double.infinity) { if (width == double.infinity) {
width = constrains.maxWidth; width = constrains.maxWidth;
height = width * cacheSize.height / cacheSize.width; height = width * cacheSize.height / cacheSize.width;
} else if(height == double.infinity) { } else if (height == double.infinity) {
height = constrains.maxHeight; height = constrains.maxHeight;
width = height * cacheSize.width / cacheSize.height; width = height * cacheSize.width / cacheSize.height;
} }
} else { } else {
if(width == double.infinity) { if (width == double.infinity) {
width = constrains.maxWidth; width = constrains.maxWidth;
height = 300; height = 300;
} else if(height == double.infinity) { } else if (height == double.infinity) {
height = constrains.maxHeight; height = constrains.maxHeight;
width = 300; width = 300;
} }
} }
if(_imageInfo != null){ if (_imageInfo != null) {
// build image // build image
Widget result = RawImage( Widget result = RawImage(
// Do not clone the image, because RawImage is a stateless wrapper. // Do not clone the image, because RawImage is a stateless wrapper.
@@ -379,12 +399,13 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
height: 24, height: 24,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 3, strokeWidth: 3,
backgroundColor: context.colorScheme.surfaceContainerLow, backgroundColor: context.colorScheme.surfaceContainer,
value: (_loadingProgress != null && value: (_loadingProgress != null &&
_loadingProgress!.expectedTotalBytes!=null && _loadingProgress!.expectedTotalBytes != null &&
_loadingProgress!.expectedTotalBytes! != 0) _loadingProgress!.expectedTotalBytes! != 0)
?_loadingProgress!.cumulativeBytesLoaded / _loadingProgress!.expectedTotalBytes! ? _loadingProgress!.cumulativeBytesLoaded /
:0, _loadingProgress!.expectedTotalBytes!
: 0,
), ),
), ),
), ),
@@ -398,8 +419,10 @@ class _ComicImageState extends State<ComicImage> with WidgetsBindingObserver {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream)); description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo)); description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress)); description.add(DiagnosticsProperty<ImageChunkEvent>(
'loadingProgress', _loadingProgress));
description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber)); description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded)); description.add(DiagnosticsProperty<bool>(
'wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
} }
} }

View File

@@ -9,7 +9,7 @@ class _ReaderGestureDetector extends StatefulWidget {
State<_ReaderGestureDetector> createState() => _ReaderGestureDetectorState(); State<_ReaderGestureDetector> createState() => _ReaderGestureDetectorState();
} }
class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> { class _ReaderGestureDetectorState extends AutomaticGlobalState<_ReaderGestureDetector> {
late TapGestureRecognizer _tapGestureRecognizer; late TapGestureRecognizer _tapGestureRecognizer;
static const _kDoubleTapMaxTime = Duration(milliseconds: 200); static const _kDoubleTapMaxTime = Duration(milliseconds: 200);
@@ -26,6 +26,12 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
late _ReaderState reader; late _ReaderState reader;
bool ignoreNextTag = false;
void ignoreNextTap() {
ignoreNextTag = true;
}
@override @override
void initState() { void initState() {
_tapGestureRecognizer = TapGestureRecognizer() _tapGestureRecognizer = TapGestureRecognizer()
@@ -44,6 +50,10 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onPointerDown: (event) { onPointerDown: (event) {
fingers++; fingers++;
if (ignoreNextTag) {
ignoreNextTag = false;
return;
}
_lastTapPointer = event.pointer; _lastTapPointer = event.pointer;
_lastTapMoveDistance = Offset.zero; _lastTapMoveDistance = Offset.zero;
_tapGestureRecognizer.addPointer(event); _tapGestureRecognizer.addPointer(event);
@@ -290,6 +300,9 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
void removeDragListener(_DragListener listener) { void removeDragListener(_DragListener listener) {
_dragListeners.remove(listener); _dragListeners.remove(listener);
} }
@override
Object? get key => "reader_gesture";
} }
class _DragListener { class _DragListener {

View File

@@ -22,6 +22,7 @@ import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/comic_type.dart';
import 'package:venera/foundation/consts.dart'; import 'package:venera/foundation/consts.dart';
import 'package:venera/foundation/favorites.dart'; import 'package:venera/foundation/favorites.dart';
import 'package:venera/foundation/global_state.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';