From 639c444a4e3a9759e42e108e97d869b492f2aece Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Sat, 25 Apr 2020 02:17:21 +0530 Subject: [PATCH] Display thumbnail while loading the actual image in the background --- lib/core/lru_map.dart | 31 ---------- lib/core/thumbnail_cache.dart | 16 ++++++ lib/ui/image_widget.dart | 10 ++-- lib/ui/zoomable_image.dart | 105 ++++++++++++++++++++-------------- 4 files changed, 83 insertions(+), 79 deletions(-) create mode 100644 lib/core/thumbnail_cache.dart diff --git a/lib/core/lru_map.dart b/lib/core/lru_map.dart index 6084383fe..3c7ec9fcd 100644 --- a/lib/core/lru_map.dart +++ b/lib/core/lru_map.dart @@ -1,5 +1,4 @@ import 'dart:collection'; -import 'dart:typed_data'; typedef EvictionHandler(K key, V value); @@ -34,33 +33,3 @@ class LRUMap { _map.remove(key); } } - -class ImageLruCache { - static LRUMap<_ImageCacheEntity, Uint8List> _map = LRUMap(500); - - static Uint8List getData(int id, [int size = 64]) { - return _map.get(_ImageCacheEntity(id, size)); - } - - static void setData(int id, int size, Uint8List imageData) { - _map.put(_ImageCacheEntity(id, size), imageData); - } -} - -class _ImageCacheEntity { - int id; - int size; - - _ImageCacheEntity(this.id, this.size); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _ImageCacheEntity && - runtimeType == other.runtimeType && - id == other.id && - size == other.size; - - @override - int get hashCode => id.hashCode ^ size.hashCode; -} diff --git a/lib/core/thumbnail_cache.dart b/lib/core/thumbnail_cache.dart new file mode 100644 index 000000000..5b0ac8e99 --- /dev/null +++ b/lib/core/thumbnail_cache.dart @@ -0,0 +1,16 @@ +import 'dart:typed_data'; + +import 'package:myapp/core/lru_map.dart'; +import 'package:myapp/models/photo.dart'; + +class ThumbnailLruCache { + static LRUMap _map = LRUMap(500); + + static Uint8List get(Photo photo) { + return _map.get(photo.generatedId); + } + + static void put(Photo photo, Uint8List imageData) { + _map.put(photo.generatedId, imageData); + } +} diff --git a/lib/ui/image_widget.dart b/lib/ui/image_widget.dart index 7432ca2bf..71a1cb263 100644 --- a/lib/ui/image_widget.dart +++ b/lib/ui/image_widget.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:myapp/core/lru_map.dart'; +import 'package:myapp/core/thumbnail_cache.dart'; import 'package:myapp/models/photo.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -25,9 +25,8 @@ class _ImageWidgetState extends State { ); @override Widget build(BuildContext context) { - final size = widget.size == null ? 124 : widget.size; - final cachedImageData = - ImageLruCache.getData(widget.photo.generatedId, size); + final size = widget.size == null ? 128 : widget.size; + final cachedImageData = ThumbnailLruCache.get(widget.photo); Widget image; @@ -40,8 +39,7 @@ class _ImageWidgetState extends State { .thumbDataWithSize(size, size), builder: (context, snapshot) { if (snapshot.hasData) { - ImageLruCache.setData( - widget.photo.generatedId, size, snapshot.data); + ThumbnailLruCache.put(widget.photo, snapshot.data); Image image = _buildImage(snapshot.data, size); return image; } else { diff --git a/lib/ui/zoomable_image.dart b/lib/ui/zoomable_image.dart index e2ed656b9..6572d22c3 100644 --- a/lib/ui/zoomable_image.dart +++ b/lib/ui/zoomable_image.dart @@ -1,70 +1,91 @@ -import 'dart:typed_data'; - import 'package:flutter/widgets.dart'; import 'package:logger/logger.dart'; import 'package:myapp/core/lru_map.dart'; +import 'package:myapp/core/thumbnail_cache.dart'; import 'package:myapp/models/photo.dart'; -import 'package:myapp/ui/image_widget.dart'; import 'package:myapp/ui/loading_widget.dart'; import 'package:photo_view/photo_view.dart'; -class WidgetLruCache { +class ExpandedImageLruCache { static LRUMap _map = LRUMap(500); - static Widget get(Photo photo) { + static Widget getData(Photo photo) { return _map.get(photo.generatedId); } - static void put(Photo photo, Widget data) { - _map.put(photo.generatedId, data); + static void setData(Photo photo, Widget image) { + _map.put(photo.generatedId, image); } } -class ZoomableImage extends StatelessWidget { +class ZoomableImage extends StatefulWidget { + final Photo photo; final Function(bool) shouldDisableScroll; - const ZoomableImage( + ZoomableImage( this.photo, { Key key, this.shouldDisableScroll, }) : super(key: key); - final Photo photo; + @override + _ZoomableImageState createState() => _ZoomableImageState(); +} + +class _ZoomableImageState extends State { + ImageProvider _imageProvider; + bool _loadedThumbnail = false; + bool _loadedFinalImage = false; + ValueChanged _scaleStateChangedCallback; + + @override + void initState() { + _scaleStateChangedCallback = (value) { + if (widget.shouldDisableScroll != null) { + widget.shouldDisableScroll(value != PhotoViewScaleState.initial); + } + }; + super.initState(); + } @override Widget build(BuildContext context) { - Logger().i("Building " + photo.toString()); - if (WidgetLruCache.get(photo) != null) { - return WidgetLruCache.get(photo); - } - Logger().i("Cache miss " + photo.toString()); - return FutureBuilder( - future: photo.getBytes(), - builder: (_, snapshot) { - if (snapshot.hasData) { - final photoView = _buildPhotoView(snapshot.data); - WidgetLruCache.put(photo, photoView); - return photoView; - } else if (snapshot.hasError) { - return Text(snapshot.error.toString()); - } else { - Logger().i("Loading"); - return ImageWidget(photo); - } - }, - ); - } - - Widget _buildPhotoView(Uint8List imageData) { - ValueChanged scaleStateChangedCallback = (value) { - if (shouldDisableScroll != null) { - shouldDisableScroll(value != PhotoViewScaleState.initial); + if (!_loadedThumbnail && !_loadedFinalImage) { + final cachedThumbnail = ThumbnailLruCache.get(widget.photo); + if (cachedThumbnail != null) { + _imageProvider = Image.memory(cachedThumbnail).image; + _loadedThumbnail = true; } - }; - return PhotoView( - imageProvider: Image.memory(imageData).image, - scaleStateChangedCallback: scaleStateChangedCallback, - minScale: PhotoViewComputedScale.contained, - ); + } + + if (!_loadedFinalImage) { + widget.photo.getBytes().then((bytes) { + if (mounted) { + setState(() { + final imageProvider = Image.memory(bytes).image; + precacheImage(imageProvider, context).then((value) { + if (mounted) { + setState(() { + _imageProvider = imageProvider; + _loadedFinalImage = true; + }); + } + }); + }); + } + }); + } + + if (_imageProvider != null) { + final view = PhotoView( + imageProvider: _imageProvider, + scaleStateChangedCallback: _scaleStateChangedCallback, + minScale: PhotoViewComputedScale.contained, + gaplessPlayback: true, + ); + return view; + } else { + return loadWidget; + } } }