Преглед на файлове

Improve thumbnail generation logic

Vishnu Mohandas преди 5 години
родител
ревизия
6c768da91a
променени са 6 файла, в които са добавени 80 реда и са изтрити 43 реда
  1. 2 0
      lib/core/constants.dart
  2. 23 5
      lib/core/thumbnail_cache.dart
  3. 5 1
      lib/models/photo.dart
  4. 1 1
      lib/ui/album_list_widget.dart
  5. 44 34
      lib/ui/thumbnail_widget.dart
  6. 5 2
      lib/ui/zoomable_image.dart

+ 2 - 0
lib/core/constants.dart

@@ -1,2 +1,4 @@
 const String ENDPOINT = "http://192.168.0.255:8080";
 const String ENDPOINT = "http://192.168.0.255:8080";
 const String USER = "umbu"; // TODO: Fix me
 const String USER = "umbu"; // TODO: Fix me
+const int THUMBNAIL_SMALL_SIZE = 128;
+const int THUMBNAIL_LARGE_SIZE = 512;

+ 23 - 5
lib/core/thumbnail_cache.dart

@@ -4,13 +4,31 @@ import 'package:myapp/core/lru_map.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/models/photo.dart';
 
 
 class ThumbnailLruCache {
 class ThumbnailLruCache {
-  static LRUMap<int, Uint8List> _map = LRUMap(500);
+  static LRUMap<_ThumbnailCacheKey, Uint8List> _map = LRUMap(5000);
 
 
-  static Uint8List get(Photo photo) {
-    return _map.get(photo.generatedId);
+  static Uint8List get(Photo photo, int size) {
+    return _map.get(_ThumbnailCacheKey(photo, size));
   }
   }
 
 
-  static void put(Photo photo, Uint8List imageData) {
-    _map.put(photo.generatedId, imageData);
+  static void put(Photo photo, int size, Uint8List imageData) {
+    _map.put(_ThumbnailCacheKey(photo, size), imageData);
   }
   }
 }
 }
+
+class _ThumbnailCacheKey {
+  Photo photo;
+  int size;
+
+  _ThumbnailCacheKey(this.photo, this.size);
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is _ThumbnailCacheKey &&
+          runtimeType == other.runtimeType &&
+          photo.generatedId == other.photo.generatedId &&
+          size == other.size;
+
+  @override
+  int get hashCode => photo.hashCode * size.hashCode;
+}

+ 5 - 1
lib/models/photo.dart

@@ -32,8 +32,12 @@ class Photo {
     return photo;
     return photo;
   }
   }
 
 
+  AssetEntity getAsset() {
+    return AssetEntity(id: localId);
+  }
+
   Future<Uint8List> getBytes({int quality = 100}) {
   Future<Uint8List> getBytes({int quality = 100}) {
-    final asset = AssetEntity(id: localId);
+    final asset = getAsset();
     if (extension(title) == ".HEIC" || quality != 100) {
     if (extension(title) == ".HEIC" || quality != 100) {
       return asset.originBytes.then((bytes) =>
       return asset.originBytes.then((bytes) =>
           FlutterImageCompress.compressWithList(bytes, quality: quality)
           FlutterImageCompress.compressWithList(bytes, quality: quality)

+ 1 - 1
lib/ui/album_list_widget.dart

@@ -59,7 +59,7 @@ class _AlbumListWidgetState extends State<AlbumListWidget> {
     return GestureDetector(
     return GestureDetector(
       child: Column(
       child: Column(
         children: <Widget>[
         children: <Widget>[
-          ThumbnailWidget(album.photos[0], size: 140),
+          ThumbnailWidget(album.photos[0]),
           Padding(padding: EdgeInsets.all(2)),
           Padding(padding: EdgeInsets.all(2)),
           Expanded(
           Expanded(
             child: Text(
             child: Text(

+ 44 - 34
lib/ui/thumbnail_widget.dart

@@ -1,18 +1,15 @@
-import 'dart:typed_data';
-
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:logger/logger.dart';
 import 'package:myapp/core/thumbnail_cache.dart';
 import 'package:myapp/core/thumbnail_cache.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/models/photo.dart';
-import 'package:photo_manager/photo_manager.dart';
+import 'package:myapp/core/constants.dart';
 
 
 class ThumbnailWidget extends StatefulWidget {
 class ThumbnailWidget extends StatefulWidget {
   final Photo photo;
   final Photo photo;
-  final int size;
 
 
   const ThumbnailWidget(
   const ThumbnailWidget(
     this.photo, {
     this.photo, {
     Key key,
     Key key,
-    this.size,
   }) : super(key: key);
   }) : super(key: key);
   @override
   @override
   _ThumbnailWidgetState createState() => _ThumbnailWidgetState();
   _ThumbnailWidgetState createState() => _ThumbnailWidgetState();
@@ -24,41 +21,54 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
     color: Colors.grey[500],
     color: Colors.grey[500],
   );
   );
 
 
+  bool _loadedSmallThumbnail = false;
+  bool _loadedLargeThumbnail = false;
+  ImageProvider _imageProvider;
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    final size = widget.size == null ? 512 : widget.size;
-    final cachedImageData = ThumbnailLruCache.get(widget.photo);
-
-    Widget image;
-
-    if (cachedImageData != null) {
-      image = _buildImage(cachedImageData, size);
-    } else {
-      if (widget.photo.localId != null) {
-        image = FutureBuilder<Uint8List>(
-          future: AssetEntity(id: widget.photo.localId)
-              .thumbDataWithSize(size, size),
-          builder: (context, snapshot) {
-            if (snapshot.hasData) {
-              ThumbnailLruCache.put(widget.photo, snapshot.data);
-              Image image = _buildImage(snapshot.data, size);
-              return image;
-            } else {
-              return loadingWidget;
-            }
-          },
-        );
+    if (!_loadedSmallThumbnail && !_loadedLargeThumbnail) {
+      final cachedSmallThumbnail =
+          ThumbnailLruCache.get(widget.photo, THUMBNAIL_SMALL_SIZE);
+      if (cachedSmallThumbnail != null) {
+        _imageProvider = Image.memory(cachedSmallThumbnail).image;
+        _loadedSmallThumbnail = true;
       } else {
       } else {
-        // TODO
-        return Text("Not Implemented");
+        if (mounted) {
+          widget.photo
+              .getAsset()
+              .thumbDataWithSize(THUMBNAIL_SMALL_SIZE, THUMBNAIL_SMALL_SIZE)
+              .then((data) {
+            if (mounted) {
+              setState(() {
+                if (data != null) {
+                  _imageProvider = Image.memory(data).image;
+                }
+                _loadedSmallThumbnail = true;
+              });
+            }
+            ThumbnailLruCache.put(widget.photo, THUMBNAIL_SMALL_SIZE, data);
+          });
+        }
       }
       }
     }
     }
 
 
-    return image;
-  }
+    if (!_loadedLargeThumbnail) {
+      if (ThumbnailLruCache.get(widget.photo, THUMBNAIL_LARGE_SIZE) == null) {
+        widget.photo
+            .getAsset()
+            .thumbDataWithSize(THUMBNAIL_LARGE_SIZE, THUMBNAIL_LARGE_SIZE)
+            .then((data) {
+          ThumbnailLruCache.put(widget.photo, THUMBNAIL_LARGE_SIZE, data);
+        });
+      }
+    }
 
 
-  Image _buildImage(Uint8List data, int size) {
-    return Image.memory(data,
-        width: size.toDouble(), height: size.toDouble(), fit: BoxFit.cover);
+    if (_imageProvider != null) {
+      return Image(
+          image: _imageProvider, gaplessPlayback: true, fit: BoxFit.cover);
+    } else {
+      return loadingWidget;
+    }
   }
   }
 }
 }

+ 5 - 2
lib/ui/zoomable_image.dart

@@ -2,11 +2,11 @@ import 'dart:typed_data';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
 import 'package:logger/logger.dart';
 import 'package:logger/logger.dart';
 import 'package:myapp/core/image_cache.dart';
 import 'package:myapp/core/image_cache.dart';
-import 'package:myapp/core/lru_map.dart';
 import 'package:myapp/core/thumbnail_cache.dart';
 import 'package:myapp/core/thumbnail_cache.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/ui/loading_widget.dart';
 import 'package:myapp/ui/loading_widget.dart';
 import 'package:photo_view/photo_view.dart';
 import 'package:photo_view/photo_view.dart';
+import 'package:myapp/core/constants.dart';
 
 
 class ZoomableImage extends StatefulWidget {
 class ZoomableImage extends StatefulWidget {
   final Photo photo;
   final Photo photo;
@@ -41,10 +41,13 @@ class _ZoomableImageState extends State<ZoomableImage> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     if (!_loadedThumbnail && !_loadedFinalImage) {
     if (!_loadedThumbnail && !_loadedFinalImage) {
-      final cachedThumbnail = ThumbnailLruCache.get(widget.photo);
+      final cachedThumbnail =
+          ThumbnailLruCache.get(widget.photo, THUMBNAIL_LARGE_SIZE);
       if (cachedThumbnail != null) {
       if (cachedThumbnail != null) {
         _imageProvider = Image.memory(cachedThumbnail).image;
         _imageProvider = Image.memory(cachedThumbnail).image;
         _loadedThumbnail = true;
         _loadedThumbnail = true;
+      } else {
+        Logger().i("Thumbnail missing for " + widget.photo.toString());
       }
       }
     }
     }