thumbnail/app icon: use display metrics in Android instead of devicePixelRatio in Flutter

This commit is contained in:
Thibault Deckers 2020-06-11 14:28:09 +09:00
parent bd4d792179
commit cbacb923e7
6 changed files with 39 additions and 23 deletions

View file

@ -121,12 +121,16 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
private void getAppIcon(MethodCall call, MethodChannel.Result result) { private void getAppIcon(MethodCall call, MethodChannel.Result result) {
String packageName = call.argument("packageName"); String packageName = call.argument("packageName");
Integer size = call.argument("size"); Double sizeDip = call.argument("sizeDip");
if (packageName == null || size == null) { if (packageName == null || sizeDip == null) {
result.error("getAppIcon-args", "failed because of missing arguments", null); result.error("getAppIcon-args", "failed because of missing arguments", null);
return; return;
} }
// convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter
float density = context.getResources().getDisplayMetrics().density;
int size = (int) Math.round(sizeDip * density);
byte[] data = null; byte[] data = null;
try { try {
int iconResourceId = context.getPackageManager().getApplicationInfo(packageName, 0).icon; int iconResourceId = context.getPackageManager().getApplicationInfo(packageName, 0).icon;

View file

@ -21,6 +21,7 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
public static final String CHANNEL = "deckers.thibault/aves/image"; public static final String CHANNEL = "deckers.thibault/aves/image";
private Activity activity; private Activity activity;
private float density;
private MediaStoreStreamHandler mediaStoreStreamHandler; private MediaStoreStreamHandler mediaStoreStreamHandler;
public ImageFileHandler(Activity activity, MediaStoreStreamHandler mediaStoreStreamHandler) { public ImageFileHandler(Activity activity, MediaStoreStreamHandler mediaStoreStreamHandler) {
@ -28,6 +29,13 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
this.mediaStoreStreamHandler = mediaStoreStreamHandler; this.mediaStoreStreamHandler = mediaStoreStreamHandler;
} }
public float getDensity() {
if (density == 0) {
density = activity.getResources().getDisplayMetrics().density;
}
return density;
}
@Override @Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) { switch (call.method) {
@ -63,13 +71,20 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
private void getThumbnail(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { private void getThumbnail(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
Map entryMap = call.argument("entry"); Map entryMap = call.argument("entry");
Integer width = call.argument("width"); Double widthDip = call.argument("widthDip");
Integer height = call.argument("height"); Double heightDip = call.argument("heightDip");
Integer defaultSize = call.argument("defaultSize"); Double defaultSizeDip = call.argument("defaultSizeDip");
if (entryMap == null || defaultSize == null) {
if (entryMap == null || widthDip == null || heightDip == null || defaultSizeDip == null) {
result.error("getThumbnail-args", "failed because of missing arguments", null); result.error("getThumbnail-args", "failed because of missing arguments", null);
return; return;
} }
// convert DIP to physical pixels here, instead of using `devicePixelRatio` in Flutter
float density = getDensity();
int width = (int) Math.round(widthDip * density);
int height = (int) Math.round(heightDip * density);
int defaultSize = (int) Math.round(defaultSizeDip * density);
ImageEntry entry = new ImageEntry(entryMap); ImageEntry entry = new ImageEntry(entryMap);
new ImageDecodeTask(activity).execute(new ImageDecodeTask.Params(entry, width, height, defaultSize, result)); new ImageDecodeTask(activity).execute(new ImageDecodeTask.Params(entry, width, height, defaultSize, result));
} }

View file

@ -16,11 +16,11 @@ class AndroidAppService {
return {}; return {};
} }
static Future<Uint8List> getAppIcon(String packageName, int size) async { static Future<Uint8List> getAppIcon(String packageName, double size) async {
try { try {
final result = await platform.invokeMethod('getAppIcon', <String, dynamic>{ final result = await platform.invokeMethod('getAppIcon', <String, dynamic>{
'packageName': packageName, 'packageName': packageName,
'size': size, 'sizeDip': size,
}); });
return result as Uint8List; return result as Uint8List;
} on PlatformException catch (e) { } on PlatformException catch (e) {

View file

@ -14,6 +14,7 @@ class ImageFileService {
static const platform = MethodChannel('deckers.thibault/aves/image'); static const platform = MethodChannel('deckers.thibault/aves/image');
static final StreamsChannel byteChannel = StreamsChannel('deckers.thibault/aves/imagebytestream'); static final StreamsChannel byteChannel = StreamsChannel('deckers.thibault/aves/imagebytestream');
static final StreamsChannel opChannel = StreamsChannel('deckers.thibault/aves/imageopstream'); static final StreamsChannel opChannel = StreamsChannel('deckers.thibault/aves/imageopstream');
static const double thumbnailDefaultSize = 64.0;
static Future<void> getImageEntries(SortFactor sort, GroupFactor group) async { static Future<void> getImageEntries(SortFactor sort, GroupFactor group) async {
try { try {
@ -76,15 +77,15 @@ class ImageFileService {
return Future.sync(() => Uint8List(0)); return Future.sync(() => Uint8List(0));
} }
static Future<Uint8List> getThumbnail(ImageEntry entry, int width, int height, {Object taskKey, int priority}) { static Future<Uint8List> getThumbnail(ImageEntry entry, double width, double height, {Object taskKey, int priority}) {
return servicePolicy.call( return servicePolicy.call(
() async { () async {
try { try {
final result = await platform.invokeMethod('getThumbnail', <String, dynamic>{ final result = await platform.invokeMethod('getThumbnail', <String, dynamic>{
'entry': entry.toMap(), 'entry': entry.toMap(),
'width': width, 'widthDip': width,
'height': height, 'heightDip': height,
'defaultSize': 256, 'defaultSizeDip': thumbnailDefaultSize,
}); });
return result as Uint8List; return result as Uint8List;
} on PlatformException catch (e) { } on PlatformException catch (e) {

View file

@ -21,7 +21,7 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
Future<AppIconImageKey> obtainKey(ImageConfiguration configuration) { Future<AppIconImageKey> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<AppIconImageKey>(AppIconImageKey( return SynchronousFuture<AppIconImageKey>(AppIconImageKey(
packageName: packageName, packageName: packageName,
sizePixels: (size * configuration.devicePixelRatio).round(), size: size,
scale: scale, scale: scale,
)); ));
} }
@ -38,28 +38,28 @@ class AppIconImage extends ImageProvider<AppIconImageKey> {
} }
Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderCallback decode) async { Future<ui.Codec> _loadAsync(AppIconImageKey key, DecoderCallback decode) async {
final bytes = await AndroidAppService.getAppIcon(key.packageName, key.sizePixels); final bytes = await AndroidAppService.getAppIcon(key.packageName, key.size);
return await decode(bytes ?? Uint8List(0)); return await decode(bytes ?? Uint8List(0));
} }
} }
class AppIconImageKey { class AppIconImageKey {
final String packageName; final String packageName;
final int sizePixels; final double size;
final double scale; final double scale;
const AppIconImageKey({ const AppIconImageKey({
@required this.packageName, @required this.packageName,
@required this.sizePixels, @required this.size,
this.scale, this.scale,
}); });
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false; if (other.runtimeType != runtimeType) return false;
return other is AppIconImageKey && other.packageName == packageName && other.sizePixels == sizePixels && other.scale == scale; return other is AppIconImageKey && other.packageName == packageName && other.size == size && other.scale == scale;
} }
@override @override
int get hashCode => hashValues(packageName, sizePixels, scale); int get hashCode => hashValues(packageName, size, scale);
} }

View file

@ -34,7 +34,6 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
ThumbnailProviderKey _buildKey(ImageConfiguration configuration) => ThumbnailProviderKey( ThumbnailProviderKey _buildKey(ImageConfiguration configuration) => ThumbnailProviderKey(
entry: entry, entry: entry,
extent: extent, extent: extent,
devicePixelRatio: configuration.devicePixelRatio,
scale: scale, scale: scale,
); );
@ -50,8 +49,7 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
} }
Future<ui.Codec> _loadAsync(ThumbnailProviderKey key, DecoderCallback decode) async { Future<ui.Codec> _loadAsync(ThumbnailProviderKey key, DecoderCallback decode) async {
final dimPixels = (extent * key.devicePixelRatio).round(); final bytes = await ImageFileService.getThumbnail(key.entry, extent, extent, taskKey: _cancellationKey);
final bytes = await ImageFileService.getThumbnail(key.entry, dimPixels, dimPixels, taskKey: _cancellationKey);
return await decode(bytes ?? Uint8List(0)); return await decode(bytes ?? Uint8List(0));
} }
@ -67,13 +65,11 @@ class ThumbnailProvider extends ImageProvider<ThumbnailProviderKey> {
class ThumbnailProviderKey { class ThumbnailProviderKey {
final ImageEntry entry; final ImageEntry entry;
final double extent; final double extent;
final double devicePixelRatio; // do not include configuration in key hashcode or == operator
final double scale; final double scale;
const ThumbnailProviderKey({ const ThumbnailProviderKey({
@required this.entry, @required this.entry,
@required this.extent, @required this.extent,
@required this.devicePixelRatio,
this.scale, this.scale,
}); });