|
@@ -35,7 +35,7 @@ class ZoomableImage extends StatefulWidget {
|
|
|
|
|
|
class _ZoomableImageState extends State<ZoomableImage>
|
|
|
with SingleTickerProviderStateMixin {
|
|
|
- final Logger _logger = Logger("ZoomableImage");
|
|
|
+ late Logger _logger;
|
|
|
late File _photo;
|
|
|
ImageProvider? _imageProvider;
|
|
|
bool _loadedSmallThumbnail = false;
|
|
@@ -45,10 +45,13 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|
|
bool _loadedFinalImage = false;
|
|
|
ValueChanged<PhotoViewScaleState>? _scaleStateChangedCallback;
|
|
|
bool _isZooming = false;
|
|
|
+ PhotoViewController _photoViewController = PhotoViewController();
|
|
|
+ int? _thumbnailWidth;
|
|
|
|
|
|
@override
|
|
|
void initState() {
|
|
|
_photo = widget.photo;
|
|
|
+ _logger = Logger("ZoomableImage_" + _photo.displayName);
|
|
|
debugPrint('initState for ${_photo.toString()}');
|
|
|
_scaleStateChangedCallback = (value) {
|
|
|
if (widget.shouldDisableScroll != null) {
|
|
@@ -61,6 +64,12 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|
|
super.initState();
|
|
|
}
|
|
|
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ _photoViewController.dispose();
|
|
|
+ super.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
if (_photo.isRemoteFile) {
|
|
@@ -75,6 +84,7 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|
|
axis: Axis.vertical,
|
|
|
child: PhotoView(
|
|
|
imageProvider: _imageProvider,
|
|
|
+ controller: _photoViewController,
|
|
|
scaleStateChangedCallback: _scaleStateChangedCallback,
|
|
|
minScale: PhotoViewComputedScale.contained,
|
|
|
gaplessPlayback: true,
|
|
@@ -106,6 +116,7 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|
|
if (cachedThumbnail != null) {
|
|
|
_imageProvider = Image.memory(cachedThumbnail).image;
|
|
|
_loadedSmallThumbnail = true;
|
|
|
+ _captureThumbnailDimensions(_imageProvider!);
|
|
|
} else {
|
|
|
getThumbnailFromServer(_photo).then((file) {
|
|
|
final imageProvider = Image.memory(file).image;
|
|
@@ -115,6 +126,7 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|
|
setState(() {
|
|
|
_imageProvider = imageProvider;
|
|
|
_loadedSmallThumbnail = true;
|
|
|
+ _captureThumbnailDimensions(_imageProvider!);
|
|
|
});
|
|
|
}
|
|
|
}).catchError((e) {
|
|
@@ -211,14 +223,52 @@ class _ZoomableImageState extends State<ZoomableImage>
|
|
|
if (mounted) {
|
|
|
precacheImage(imageProvider, context).then((value) {
|
|
|
if (mounted) {
|
|
|
+ _updatePhotoViewController(imageProvider);
|
|
|
setState(() {
|
|
|
_imageProvider = imageProvider;
|
|
|
_loadedFinalImage = true;
|
|
|
+ _logger.info("Final image loaded");
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ void _captureThumbnailDimensions(ImageProvider imageProvider) {
|
|
|
+ imageProvider.resolve(const ImageConfiguration()).addListener(
|
|
|
+ ImageStreamListener(
|
|
|
+ ((imageInfo, _) {
|
|
|
+ _thumbnailWidth = imageInfo.image.width;
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ void _updatePhotoViewController(ImageProvider<Object> imageProvider) {
|
|
|
+ imageProvider.resolve(const ImageConfiguration()).addListener(
|
|
|
+ ImageStreamListener(
|
|
|
+ ((imageInfo, _) {
|
|
|
+ if (_loadedFinalImage ||
|
|
|
+ _thumbnailWidth == null ||
|
|
|
+ _photoViewController.scale == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ final scale = _photoViewController.scale! /
|
|
|
+ (imageInfo.image.width / _thumbnailWidth!);
|
|
|
+ final currentPosition = _photoViewController.value.position;
|
|
|
+ final positionScaleFactor = 1 / scale;
|
|
|
+ final newPosition = currentPosition.scale(
|
|
|
+ positionScaleFactor,
|
|
|
+ positionScaleFactor,
|
|
|
+ );
|
|
|
+ _photoViewController = PhotoViewController(
|
|
|
+ initialPosition: newPosition,
|
|
|
+ initialScale: scale,
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
bool _isGIF() => _photo.displayName.toLowerCase().endsWith(".gif");
|
|
|
}
|