mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
Improve comic image loading retry
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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';
|
||||||
|
Reference in New Issue
Block a user