diff --git a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java index f3b5bbe56..3d5f72129 100644 --- a/android/app/src/main/java/deckers/thibault/aves/MainActivity.java +++ b/android/app/src/main/java/deckers/thibault/aves/MainActivity.java @@ -77,10 +77,10 @@ public class MainActivity extends FlutterActivity { new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( (call, result) -> { switch (call.method) { - case "getImages": + case "getImageEntries": getPermissionResult(result, this); break; - case "getThumbnail": { + case "getImageBytes": { Map map = call.argument("entry"); Integer width = call.argument("width"); Integer height = call.argument("height"); @@ -88,7 +88,7 @@ public class MainActivity extends FlutterActivity { thumbnailFetcher.fetch(entry, width, height, result); break; } - case "cancelGetThumbnail": { + case "cancelGetImageBytes": { String uri = call.argument("uri"); thumbnailFetcher.cancel(uri); result.success(null); @@ -198,7 +198,7 @@ class BitmapWorkerTask extends AsyncTask target = Glide.with(activity) @@ -218,7 +218,7 @@ class BitmapWorkerTask extends AsyncTask { maxCrossAxisExtent: extent, ), itemBuilder: (gridContext, index) { - var imageEntryMap = imageEntryList[index] as Map; - return GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (context) => ImageFullscreenPage(entry: imageEntryMap)), - ), - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: Colors.grey.shade700, - width: 0.5, - ), - ), - child: Thumbnail( - entry: imageEntryMap, - extent: extent, - ), - ), + return Thumbnail( + entry: imageEntryList[index] as Map, + extent: extent, ); }, itemCount: imageEntryList.length, diff --git a/lib/settings.dart b/lib/settings.dart new file mode 100644 index 000000000..7e617d66e --- /dev/null +++ b/lib/settings.dart @@ -0,0 +1,7 @@ +final Settings settings = Settings._private(); + +class Settings { + double devicePixelRatio; + + Settings._private(); +} \ No newline at end of file diff --git a/lib/thumbnail.dart b/lib/thumbnail.dart index b0c420686..a7f243420 100644 --- a/lib/thumbnail.dart +++ b/lib/thumbnail.dart @@ -1,7 +1,10 @@ +import 'dart:math'; import 'dart:typed_data'; +import 'package:aves/image_fullscreen_page.dart'; import 'package:aves/main.dart'; import 'package:aves/mime_types.dart'; +import 'package:aves/settings.dart'; import 'package:flutter/material.dart'; import 'package:transparent_image/transparent_image.dart'; @@ -17,16 +20,20 @@ class Thumbnail extends StatefulWidget { class ThumbnailState extends State { Future loader; + Uint8List bytes; + + String get uri => widget.entry['uri']; @override void initState() { super.initState(); - loader = ImageFetcher.getThumbnail(widget.entry, widget.extent, widget.extent); + var dim = (widget.extent * settings.devicePixelRatio).round(); + loader = ImageFetcher.getImageBytes(widget.entry, dim, dim); } @override void dispose() { - ImageFetcher.cancelGetThumbnail(widget.entry['uri']); + ImageFetcher.cancelGetImageBytes(uri); super.dispose(); } @@ -36,35 +43,64 @@ class ThumbnailState extends State { var isVideo = mimeType.startsWith(MimeTypes.MIME_VIDEO); var isGif = mimeType == MimeTypes.MIME_GIF; var iconSize = widget.extent / 4; - return FutureBuilder( - future: loader, - builder: (futureContext, AsyncSnapshot snapshot) { - var ready = snapshot.connectionState == ConnectionState.done && !snapshot.hasError; - Uint8List bytes = ready ? snapshot.data : kTransparentImage; - return Hero( - tag: widget.entry['uri'], - child: Stack( - alignment: AlignmentDirectional.bottomStart, - children: [ - Image.memory( - bytes, - width: widget.extent, - height: widget.extent, - fit: BoxFit.cover, - ), - if (isVideo) - Icon( - Icons.play_circle_outline, - size: iconSize, + return GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ImageFullscreenPage(entry: widget.entry, thumbnail: bytes), + ), + ), + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.shade700, + width: 0.5, + ), + ), + child: FutureBuilder( + future: loader, + builder: (futureContext, AsyncSnapshot snapshot) { + if (bytes == null && snapshot.connectionState == ConnectionState.done && !snapshot.hasError) { + bytes = snapshot.data; + } + return Stack( + alignment: AlignmentDirectional.bottomStart, + children: [ + Hero( + tag: uri, + child: LayoutBuilder( + builder: (context, constraints) { + // during hero animation back from a fullscreen image, + // the image covers the whole screen (because of the 'fit' prop and the full screen hero constraints) + // so we wrap the image to apply better constraints + var dim = min(constraints.maxWidth, constraints.maxHeight); + return Container( + alignment: Alignment.center, + constraints: BoxConstraints.tight(Size(dim, dim)), + child: Image.memory( + bytes ?? kTransparentImage, + width: dim, + height: dim, + fit: BoxFit.cover, + ), + ); + } + ), ), - if (isGif) - Icon( - Icons.gif, - size: iconSize, - ), - ], - ), - ); - }); + if (isVideo) + Icon( + Icons.play_circle_outline, + size: iconSize, + ), + if (isGif) + Icon( + Icons.gif, + size: iconSize, + ), + ], + ); + }), + ), + ); } }