Browse Source

Enable rendering of HEIC images

Vishnu Mohandas 5 years ago
parent
commit
20f4f6324b

+ 17 - 0
android/.project

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>android</name>
+	<comment>Project android created by Buildship.</comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
+	</natures>
+</projectDescription>

+ 13 - 0
android/.settings/org.eclipse.buildship.core.prefs

@@ -0,0 +1,13 @@
+arguments=
+auto.sync=false
+build.scans.enabled=false
+connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
+connection.project.dir=
+eclipse.preferences.version=1
+gradle.user.home=
+java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home
+jvm.arguments=
+offline.mode=false
+override.workspace.settings=true
+show.console.view=true
+show.executions.view=true

+ 6 - 0
android/app/.classpath

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
+	<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
+	<classpathentry kind="output" path="bin/default"/>
+</classpath>

+ 23 - 0
android/app/.project

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>app</name>
+	<comment>Project app created by Buildship.</comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
+	</natures>
+</projectDescription>

+ 2 - 0
android/app/.settings/org.eclipse.buildship.core.prefs

@@ -0,0 +1,2 @@
+connection.project.dir=..
+eclipse.preferences.version=1

+ 38 - 0
ios/Podfile.lock

@@ -1,16 +1,38 @@
 PODS:
   - Flutter (1.0.0)
+  - flutter_image_compress (0.0.1):
+    - Flutter
+    - Mantle
+    - SDWebImageWebPCoder
   - FMDB (2.7.5):
     - FMDB/standard (= 2.7.5)
   - FMDB/standard (2.7.5)
+  - libwebp (1.1.0):
+    - libwebp/demux (= 1.1.0)
+    - libwebp/mux (= 1.1.0)
+    - libwebp/webp (= 1.1.0)
+  - libwebp/demux (1.1.0):
+    - libwebp/webp
+  - libwebp/mux (1.1.0):
+    - libwebp/demux
+  - libwebp/webp (1.1.0)
   - local_image_provider (0.0.1):
     - Flutter
+  - Mantle (2.1.1):
+    - Mantle/extobjc (= 2.1.1)
+  - Mantle/extobjc (2.1.1)
   - path_provider (0.0.1):
     - Flutter
   - path_provider_macos (0.0.1):
     - Flutter
   - photo_manager (0.0.1):
     - Flutter
+  - SDWebImage/Core (5.7.3)
+  - SDWebImageWebPCoder (0.6.1):
+    - libwebp (~> 1.0)
+    - SDWebImage/Core (~> 5.7)
+  - share_extend (0.0.1):
+    - Flutter
   - shared_preferences (0.0.1):
     - Flutter
   - shared_preferences_macos (0.0.1):
@@ -23,10 +45,12 @@ PODS:
 
 DEPENDENCIES:
   - Flutter (from `Flutter`)
+  - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`)
   - local_image_provider (from `.symlinks/plugins/local_image_provider/ios`)
   - path_provider (from `.symlinks/plugins/path_provider/ios`)
   - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
   - photo_manager (from `.symlinks/plugins/photo_manager/ios`)
+  - share_extend (from `.symlinks/plugins/share_extend/ios`)
   - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
   - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
   - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
@@ -35,10 +59,16 @@ DEPENDENCIES:
 SPEC REPOS:
   trunk:
     - FMDB
+    - libwebp
+    - Mantle
+    - SDWebImage
+    - SDWebImageWebPCoder
 
 EXTERNAL SOURCES:
   Flutter:
     :path: Flutter
+  flutter_image_compress:
+    :path: ".symlinks/plugins/flutter_image_compress/ios"
   local_image_provider:
     :path: ".symlinks/plugins/local_image_provider/ios"
   path_provider:
@@ -47,6 +77,8 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/path_provider_macos/ios"
   photo_manager:
     :path: ".symlinks/plugins/photo_manager/ios"
+  share_extend:
+    :path: ".symlinks/plugins/share_extend/ios"
   shared_preferences:
     :path: ".symlinks/plugins/shared_preferences/ios"
   shared_preferences_macos:
@@ -58,11 +90,17 @@ EXTERNAL SOURCES:
 
 SPEC CHECKSUMS:
   Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
+  flutter_image_compress: 082f8daaf6c1b0c9fe798251c750ef0ecd98d7ae
   FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
+  libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
   local_image_provider: cf979b81bc1cacc81867d3511a1b6b7216411e93
+  Mantle: 35238ae6f2e2b2d474fa7b67fee82a59fea71915
   path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
   path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
   photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67
+  SDWebImage: 97351f6582ceca541ea294ba66a1fcb342a331c2
+  SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
+  share_extend: b6748dc53695587891126a89533b862b92548c7b
   shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01
   shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
   shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9

+ 7 - 20
ios/Runner.xcodeproj/project.pbxproj

@@ -10,11 +10,7 @@
 		06CC0CFA92976FDBA53FAAE5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB5F531E492C7002EAC4CE42 /* Pods_Runner.framework */; };
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
-		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
-		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
-		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
-		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -27,8 +23,6 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
-				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
-				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
 			);
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -39,14 +33,12 @@
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
 		6A8718E36D8B0CC6360ADB0D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
-		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
 		97A6D25F159DA10C77E8A78D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@@ -62,8 +54,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
-				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
 				06CC0CFA92976FDBA53FAAE5 /* Pods_Runner.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -74,9 +64,7 @@
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
-				3B80C3931E831B6300D905FE /* App.framework */,
 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
-				9740EEBA1CF902C7004384FC /* Flutter.framework */,
 				9740EEB21CF90195004384FC /* Debug.xcconfig */,
 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
 				9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -133,7 +121,6 @@
 				A3AB65855D82977B2D2D5B35 /* Pods-Runner.release.xcconfig */,
 				6A8718E36D8B0CC6360ADB0D /* Pods-Runner.profile.xcconfig */,
 			);
-			name = Pods;
 			path = Pods;
 			sourceTree = "<group>";
 		};
@@ -232,7 +219,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
 		};
 		481FE5B058006945E0569431 /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -376,7 +363,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				DEVELOPMENT_TEAM = 37L77XPSDP;
@@ -391,7 +378,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = Orma.FlutterApp;
+				PRODUCT_BUNDLE_IDENTIFIER = orma.app;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -515,7 +502,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				DEVELOPMENT_TEAM = 37L77XPSDP;
@@ -530,7 +517,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = Orma.FlutterApp;
+				PRODUCT_BUNDLE_IDENTIFIER = orma.app;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -547,7 +534,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
-				CODE_SIGN_IDENTITY = "iPhone Developer";
+				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				DEVELOPMENT_TEAM = 37L77XPSDP;
@@ -562,7 +549,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = Orma.FlutterApp;
+				PRODUCT_BUNDLE_IDENTIFIER = orma.app;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

+ 2 - 2
ios/Runner/Info.plist

@@ -22,6 +22,8 @@
 	<string>$(FLUTTER_BUILD_NUMBER)</string>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
+	<key>NSPhotoLibraryUsageDescription</key>
+	<string>Photo Library Access Warning</string>
 	<key>UILaunchStoryboardName</key>
 	<string>LaunchScreen</string>
 	<key>UIMainStoryboardFile</key>
@@ -41,7 +43,5 @@
 	</array>
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<false/>
-	<key>NSPhotoLibraryUsageDescription</key>
-	<string>Photo Library Access Warning</string>
 </dict>
 </plist>

+ 10 - 11
lib/core/lru_map.dart

@@ -1,6 +1,5 @@
 import 'dart:collection';
-
-import 'package:flutter/material.dart';
+import 'dart:typed_data';
 
 typedef EvictionHandler<K, V>(K key, V value);
 
@@ -37,31 +36,31 @@ class LRUMap<K, V> {
 }
 
 class ImageLruCache {
-  static LRUMap<_ImageCacheEntity, Image> _map = LRUMap(500);
+  static LRUMap<_ImageCacheEntity, Uint8List> _map = LRUMap(500);
 
-  static Image getData(String path, [int size = 64]) {
-    return _map.get(_ImageCacheEntity(path, size));
+  static Uint8List getData(int id, [int size = 64]) {
+    return _map.get(_ImageCacheEntity(id, size));
   }
 
-  static void setData(String path, int size, Image image) {
-    _map.put(_ImageCacheEntity(path, size), image);
+  static void setData(int id, int size, Uint8List imageData) {
+    _map.put(_ImageCacheEntity(id, size), imageData);
   }
 }
 
 class _ImageCacheEntity {
-  String path;
+  int id;
   int size;
 
-  _ImageCacheEntity(this.path, this.size);
+  _ImageCacheEntity(this.id, this.size);
 
   @override
   bool operator ==(Object other) =>
       identical(this, other) ||
       other is _ImageCacheEntity &&
           runtimeType == other.runtimeType &&
-          path == other.path &&
+          id == other.id &&
           size == other.size;
 
   @override
-  int get hashCode => path.hashCode ^ size.hashCode;
+  int get hashCode => id.hashCode ^ size.hashCode;
 }

+ 7 - 4
lib/models/photo.dart

@@ -1,3 +1,4 @@
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:crypto/crypto.dart';
@@ -47,14 +48,16 @@ class Photo {
     return photo;
   }
 
+  AssetEntity getAsset() {
+    return AssetEntity(id: localId);
+  }
+
   static String getHash(File file) {
     return sha256.convert(file.readAsBytesSync()).toString();
   }
 
-  int get hashCode => generatedId;
-
   @override
-  bool operator ==(other) {
-    return generatedId == other.generatedId;
+  String toString() {
+    return 'Photo(generatedId: $generatedId, uploadedFileId: $uploadedFileId, localId: $localId, path: $path, localPath: $localPath, relativePath: $relativePath, thumbnailPath: $thumbnailPath, hash: $hash, createTimestamp: $createTimestamp, syncTimestamp: $syncTimestamp)';
   }
 }

+ 86 - 31
lib/ui/detail_page.dart

@@ -1,11 +1,16 @@
-import 'dart:io';
+import 'dart:typed_data';
 
 import 'package:flutter/material.dart';
+import 'package:logger/logger.dart';
 import 'package:myapp/core/lru_map.dart';
 import 'package:myapp/models/photo.dart';
+import 'package:photo_manager/photo_manager.dart';
 import 'package:photo_view/photo_view.dart';
 import 'package:share_extend/share_extend.dart';
 import 'extents_page_view.dart';
+import 'loading_widget.dart';
+import 'package:path/path.dart' as path;
+import 'package:flutter_image_compress/flutter_image_compress.dart';
 
 class DetailPage extends StatefulWidget {
   final List<Photo> photos;
@@ -20,28 +25,38 @@ class DetailPage extends StatefulWidget {
 class _DetailPageState extends State<DetailPage> {
   bool _shouldDisableScroll = false;
   int _selectedIndex = 0;
+  final _cachedImages = LRUMap<int, ZoomableImage>(5);
+
+  @override
+  void initState() {
+    super.initState();
+  }
 
   @override
   Widget build(BuildContext context) {
     _selectedIndex = widget.selectedIndex;
+
+    Logger().i("Loading " + widget.photos[_selectedIndex].toString());
     var pageController = PageController(initialPage: _selectedIndex);
     return Scaffold(
-      appBar: AppBar(
-        actions: <Widget>[
-          IconButton(
-            icon: Icon(Icons.share),
-            onPressed: () {
-              ShareExtend.share(
-                  widget.photos[_selectedIndex].localPath, "image");
-            },
-          )
-        ],
-      ),
+      appBar: _buildAppBar(),
       body: Center(
         child: Container(
           child: ExtentsPageView.extents(
             itemBuilder: (context, index) {
-              return _buildItem(context, widget.photos[index]);
+              if (_cachedImages.get(index) != null) {
+                return _cachedImages.get(index);
+              }
+              final image = ZoomableImage(
+                photo: widget.photos[index],
+                shouldDisableScroll: (value) {
+                  setState(() {
+                    _shouldDisableScroll = value;
+                  });
+                },
+              );
+              _cachedImages.put(index, image);
+              return image;
             },
             onPageChanged: (int index) {
               _selectedIndex = index;
@@ -57,28 +72,68 @@ class _DetailPageState extends State<DetailPage> {
     );
   }
 
-  Widget _buildItem(BuildContext context, Photo photo) {
-    var image = ImageLruCache.getData(photo.localPath) == null
-        ? Image.file(
-            File(photo.localPath),
-            filterQuality: FilterQuality.low,
-          )
-        : ImageLruCache.getData(photo.localPath);
+  AppBar _buildAppBar() {
+    return AppBar(
+      actions: <Widget>[
+        IconButton(
+          icon: Icon(Icons.share),
+          onPressed: () {
+            ShareExtend.share(widget.photos[_selectedIndex].localPath, "image");
+          },
+        )
+      ],
+    );
+  }
+}
+
+class ZoomableImage extends StatelessWidget {
+  final Function(bool) shouldDisableScroll;
+
+  const ZoomableImage({
+    Key key,
+    @required this.photo,
+    this.shouldDisableScroll,
+  }) : super(key: key);
+
+  final Photo photo;
+
+  @override
+  Widget build(BuildContext context) {
+    Logger().i("Building " + photo.generatedId.toString());
+    if (ImageLruCache.getData(photo.generatedId) != null) {
+      return _buildPhotoView(ImageLruCache.getData(photo.generatedId));
+    }
+    var future;
+    if (path.extension(photo.localPath) == '.HEIC') {
+      Logger().i("Decoding HEIC");
+      future = photo.getAsset().originBytes.then((bytes) =>
+          FlutterImageCompress.compressWithList(bytes)
+              .then((result) => Uint8List.fromList(result)));
+    } else {
+      future = AssetEntity(id: photo.localId).originBytes;
+    }
+    return FutureBuilder<Uint8List>(
+      future: future,
+      builder: (_, snapshot) {
+        if (snapshot.hasData) {
+          return _buildPhotoView(snapshot.data);
+        } else if (snapshot.hasError) {
+          return Text(snapshot.error);
+        } else {
+          return loadWidget;
+        }
+      },
+    );
+  }
+
+  Widget _buildPhotoView(Uint8List imageData) {
     ValueChanged<PhotoViewScaleState> scaleStateChangedCallback = (value) {
-      var shouldDisableScroll;
-      if (value == PhotoViewScaleState.initial) {
-        shouldDisableScroll = false;
-      } else {
-        shouldDisableScroll = true;
-      }
-      if (shouldDisableScroll != _shouldDisableScroll) {
-        setState(() {
-          _shouldDisableScroll = shouldDisableScroll;
-        });
+      if (shouldDisableScroll != null) {
+        shouldDisableScroll(value != PhotoViewScaleState.initial);
       }
     };
     return PhotoView(
-      imageProvider: image.image,
+      imageProvider: Image.memory(imageData).image,
       scaleStateChangedCallback: scaleStateChangedCallback,
       minScale: PhotoViewComputedScale.contained,
     );

+ 7 - 6
lib/ui/image_widget.dart

@@ -26,14 +26,14 @@ class _ImageWidgetState extends State<ImageWidget> {
   );
   @override
   Widget build(BuildContext context) {
-    final path = widget.photo.localPath;
     final size = widget.size == null ? 124 : widget.size;
-    final cachedImage = ImageLruCache.getData(path, size);
+    final cachedImageData =
+        ImageLruCache.getData(widget.photo.generatedId, size);
 
     Widget image;
 
-    if (cachedImage != null) {
-      image = cachedImage;
+    if (cachedImageData != null) {
+      image = Image.memory(cachedImageData);
     } else {
       if (widget.photo.localId != null) {
         image = FutureBuilder<Uint8List>(
@@ -41,11 +41,12 @@ class _ImageWidgetState extends State<ImageWidget> {
               .thumbDataWithSize(size, size),
           builder: (context, snapshot) {
             if (snapshot.hasData) {
+              ImageLruCache.setData(
+                  widget.photo.generatedId, size, snapshot.data);
               Image image = Image.memory(snapshot.data,
                   width: size.toDouble(),
                   height: size.toDouble(),
                   fit: BoxFit.cover);
-              ImageLruCache.setData(path, size, image);
               return image;
             } else {
               return loadingWidget;
@@ -64,7 +65,7 @@ class _ImageWidgetState extends State<ImageWidget> {
   @override
   void didUpdateWidget(ImageWidget oldWidget) {
     super.didUpdateWidget(oldWidget);
-    if (widget.photo.localPath != oldWidget.photo.localPath) {
+    if (widget.photo.generatedId != oldWidget.photo.generatedId) {
       setState(() {});
     }
   }

+ 7 - 0
pubspec.lock

@@ -83,6 +83,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_image_compress:
+    dependency: "direct main"
+    description:
+      name: flutter_image_compress
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.6.7"
   flutter_test:
     dependency: "direct dev"
     description: flutter

+ 1 - 0
pubspec.yaml

@@ -37,6 +37,7 @@ dependencies:
   share_extend: "^1.1.2"
   draggable_scrollbar: ^0.0.4
   photo_view: ^0.9.2
+  flutter_image_compress: ^0.6.5+1
 
 dev_dependencies:
   flutter_test: