From 9357a49f4a5f5fd79961852cd4e1f92ddf3b6a16 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 17 Mar 2020 16:54:57 +0900 Subject: [PATCH] fullscreen: decode image from URI instead of path --- .../channelhandlers/ImageFileHandler.java | 39 +++++++++++++ lib/model/image_file_service.dart | 12 ++++ lib/widgets/fullscreen/image_uri.dart | 55 +++++++++++++++++++ lib/widgets/fullscreen/image_view.dart | 6 +- 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 lib/widgets/fullscreen/image_uri.dart diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java index b0dd3ba4c..f363074e6 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/ImageFileHandler.java @@ -7,6 +7,9 @@ import android.os.Looper; import androidx.annotation.NonNull; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Map; import deckers.thibault.aves.model.ImageEntry; @@ -38,6 +41,9 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler { case "getImageEntry": getImageEntry(call, result); break; + case "readAsBytes": + readAsBytes(call, result); + break; case "getImageBytes": getImageBytes(call, result); break; @@ -59,6 +65,25 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler { } } + private void readAsBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + String uri = call.argument("uri"); + + byte[] data = null; + try (InputStream is = activity.getContentResolver().openInputStream(Uri.parse(uri))) { + if (is != null) { + data = getBytes(is); + } + } catch (IOException ex) { + // ignore + } + + if (data != null) { + result.success(data); + } else { + result.error("readAsBytes-null", "failed to read bytes from uri=" + uri, null); + } + } + private void getImageBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Map entryMap = call.argument("entry"); Integer width = call.argument("width"); @@ -189,4 +214,18 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler { } }); } + + // convenience methods + + private byte[] getBytes(InputStream inputStream) throws IOException { + ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + byteBuffer.write(buffer, 0, len); + } + return byteBuffer.toByteArray(); + } } \ No newline at end of file diff --git a/lib/model/image_file_service.dart b/lib/model/image_file_service.dart index 208bda5b0..14b5f07cc 100644 --- a/lib/model/image_file_service.dart +++ b/lib/model/image_file_service.dart @@ -29,6 +29,18 @@ class ImageFileService { return null; } + static Future readAsBytes(String uri) async { + try { + final result = await platform.invokeMethod('readAsBytes', { + 'uri': uri, + }); + return result as Uint8List; + } on PlatformException catch (e) { + debugPrint('readAsBytes failed with exception=${e.message}'); + } + return Uint8List(0); + } + static Future getImageBytes(ImageEntry entry, int width, int height) async { if (width > 0 && height > 0) { // debugPrint('getImageBytes width=$width path=${entry.path}'); diff --git a/lib/widgets/fullscreen/image_uri.dart b/lib/widgets/fullscreen/image_uri.dart new file mode 100644 index 000000000..17504325a --- /dev/null +++ b/lib/widgets/fullscreen/image_uri.dart @@ -0,0 +1,55 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui show Codec; + +import 'package:aves/model/image_file_service.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class UriImage extends ImageProvider { + const UriImage(this.uri, {this.scale = 1.0}) + : assert(uri != null), + assert(scale != null); + + final String uri; + + final double scale; + + @override + Future obtainKey(ImageConfiguration configuration) { + return SynchronousFuture(this); + } + + @override + ImageStreamCompleter load(UriImage key, DecoderCallback decode) { + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode), + scale: key.scale, + informationCollector: () sync* { + yield ErrorDescription('Uri: $uri'); + }, + ); + } + + Future _loadAsync(UriImage key, DecoderCallback decode) async { + assert(key == this); + + final Uint8List bytes = await ImageFileService.readAsBytes(uri); + if (bytes.lengthInBytes == 0) { + return null; + } + + return await decode(bytes); + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) return false; + return other is UriImage && other.uri == uri && other.scale == scale; + } + + @override + int get hashCode => hashValues(uri, scale); + + @override + String toString() => '${objectRuntimeType(this, 'UriImage')}("$uri", scale: $scale)'; +} diff --git a/lib/widgets/fullscreen/image_view.dart b/lib/widgets/fullscreen/image_view.dart index 89ee355a4..61cacb82a 100644 --- a/lib/widgets/fullscreen/image_view.dart +++ b/lib/widgets/fullscreen/image_view.dart @@ -1,10 +1,8 @@ -import 'dart:io'; - import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/fullscreen/image_uri.dart'; import 'package:aves/widgets/fullscreen/video.dart'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:transparent_image/transparent_image.dart'; import 'package:tuple/tuple.dart'; import 'package:video_player/video_player.dart'; @@ -48,7 +46,7 @@ class ImageView extends StatelessWidget { return PhotoView( // key includes size and orientation to refresh when the image is rotated key: ValueKey('${entry.orientationDegrees}_${entry.width}_${entry.height}_${entry.path}'), - imageProvider: entry.path != null ? FileImage(File(entry.path)) : MemoryImage(kTransparentImage), + imageProvider: UriImage(entry.uri), loadingBuilder: (context, event) => const Center( child: SizedBox( width: 64,