Android Q: use new API to get thumbnail, fixed MediaStore update
fullscreen: evict image cache after rotation init: directly use event sink instead of stream when getting MediaStore items
This commit is contained in:
parent
5fd7ab2fa6
commit
e33b365c5d
5 changed files with 101 additions and 73 deletions
|
@ -19,6 +19,7 @@ import com.bumptech.glide.request.RequestOptions;
|
||||||
import com.bumptech.glide.signature.ObjectKey;
|
import com.bumptech.glide.signature.ObjectKey;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import deckers.thibault.aves.decoder.VideoThumbnail;
|
import deckers.thibault.aves.decoder.VideoThumbnail;
|
||||||
|
@ -66,12 +67,11 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
||||||
Params p = params[0];
|
Params p = params[0];
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
if (!this.isCancelled()) {
|
if (!this.isCancelled()) {
|
||||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
// bitmap = getBytesByResolverThumbnail(p);
|
bitmap = getThumbnailBytesByResolver(p);
|
||||||
// } else {
|
} else {
|
||||||
bitmap = getBytesByMediaStoreThumbnail(p);
|
bitmap = getThumbnailBytesByMediaStore(p);
|
||||||
// bitmap = getBytesByGlide(p);
|
}
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(LOG_TAG, "getImageBytes with uri=" + p.entry.getUri() + " cancelled");
|
Log.d(LOG_TAG, "getImageBytes with uri=" + p.entry.getUri() + " cancelled");
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.Q)
|
@TargetApi(Build.VERSION_CODES.Q)
|
||||||
private Bitmap getBytesByResolverThumbnail(Params params) {
|
private Bitmap getThumbnailBytesByResolver(Params params) {
|
||||||
ImageEntry entry = params.entry;
|
ImageEntry entry = params.entry;
|
||||||
int width = params.width;
|
int width = params.width;
|
||||||
int height = params.height;
|
int height = params.height;
|
||||||
|
@ -95,13 +95,13 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
||||||
ContentResolver resolver = activity.getContentResolver();
|
ContentResolver resolver = activity.getContentResolver();
|
||||||
try {
|
try {
|
||||||
return resolver.loadThumbnail(entry.getUri(), new Size(width, height), null);
|
return resolver.loadThumbnail(entry.getUri(), new Size(width, height), null);
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
Log.e(LOG_TAG, "failed to load thumbnail for uri=" + entry.getUri(), e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap getBytesByMediaStoreThumbnail(Params params) {
|
private Bitmap getThumbnailBytesByMediaStore(Params params) {
|
||||||
ImageEntry entry = params.entry;
|
ImageEntry entry = params.entry;
|
||||||
long contentId = entry.getContentId();
|
long contentId = entry.getContentId();
|
||||||
|
|
||||||
|
@ -113,12 +113,12 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
||||||
return MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null);
|
return MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
Log.e(LOG_TAG, "failed to get thumbnail for uri=" + entry.getUri(), e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap getBytesByGlide(Params params) {
|
private Bitmap getImageBytesByGlide(Params params) {
|
||||||
ImageEntry entry = params.entry;
|
ImageEntry entry = params.entry;
|
||||||
int width = params.width;
|
int width = params.width;
|
||||||
int height = params.height;
|
int height = params.height;
|
||||||
|
|
|
@ -32,9 +32,7 @@ public class MediaStoreStreamHandler implements EventChannel.StreamHandler {
|
||||||
void fetchAll(Activity activity) {
|
void fetchAll(Activity activity) {
|
||||||
Log.d(LOG_TAG, "fetchAll start");
|
Log.d(LOG_TAG, "fetchAll start");
|
||||||
Instant start = Instant.now();
|
Instant start = Instant.now();
|
||||||
Stream<ImageEntry> stream = new MediaStoreImageProvider().fetchAll(activity); // 100ms
|
new MediaStoreImageProvider().fetchAll(activity, eventSink); // 350ms
|
||||||
stream.map(ImageEntry::toMap)
|
|
||||||
.forEach(entry -> eventSink.success(entry)); // 250ms
|
|
||||||
eventSink.endOfStream();
|
eventSink.endOfStream();
|
||||||
Log.d(LOG_TAG, "fetchAll complete in " + Duration.between(start, Instant.now()).toMillis() + "ms");
|
Log.d(LOG_TAG, "fetchAll complete in " + Duration.between(start, Instant.now()).toMillis() + "ms");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package deckers.thibault.aves.model.provider;
|
package deckers.thibault.aves.model.provider;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
@ -10,6 +11,7 @@ import android.graphics.Matrix;
|
||||||
import android.media.ExifInterface;
|
import android.media.ExifInterface;
|
||||||
import android.media.MediaScannerConnection;
|
import android.media.MediaScannerConnection;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -32,6 +34,8 @@ import deckers.thibault.aves.utils.Utils;
|
||||||
public abstract class ImageProvider {
|
public abstract class ImageProvider {
|
||||||
private static final String LOG_TAG = Utils.createLogTag(ImageProvider.class);
|
private static final String LOG_TAG = Utils.createLogTag(ImageProvider.class);
|
||||||
|
|
||||||
|
private static Uri FILES_URI = MediaStore.Files.getContentUri("external");
|
||||||
|
|
||||||
public void delete(final Activity activity, final String path, final Uri uri, final ImageOpCallback callback) {
|
public void delete(final Activity activity, final String path, final Uri uri, final ImageOpCallback callback) {
|
||||||
callback.onFailure();
|
callback.onFailure();
|
||||||
}
|
}
|
||||||
|
@ -87,7 +91,7 @@ public abstract class ImageProvider {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
long contentId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
|
long contentId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
|
||||||
Uri itemUri = ContentUris.withAppendedId(MediaStoreImageProvider.FILES_URI, contentId);
|
Uri itemUri = ContentUris.withAppendedId(FILES_URI, contentId);
|
||||||
newFields.put("uri", itemUri.toString());
|
newFields.put("uri", itemUri.toString());
|
||||||
newFields.put("contentId", contentId);
|
newFields.put("contentId", contentId);
|
||||||
newFields.put("path", cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)));
|
newFields.put("path", cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)));
|
||||||
|
@ -168,15 +172,28 @@ public abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update fields in media store
|
// update fields in media store
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
int orientationDegrees = MetadataHelper.getOrientationDegreesForExifCode(newOrientationCode);
|
int orientationDegrees = MetadataHelper.getOrientationDegreesForExifCode(newOrientationCode);
|
||||||
values.put(MediaStore.Images.Media.ORIENTATION, orientationDegrees);
|
Map<String, Object> newFields = new HashMap<>();
|
||||||
if (activity.getContentResolver().update(uri, values, null, null) > 0) {
|
newFields.put("orientationDegrees", orientationDegrees);
|
||||||
|
|
||||||
|
ContentResolver contentResolver = activity.getContentResolver();
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
// from Android Q, media store update needs to be flagged IS_PENDING first
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
values.put(MediaStore.MediaColumns.IS_PENDING, 1);
|
||||||
|
contentResolver.update(uri, values, null, null);
|
||||||
|
values.clear();
|
||||||
|
values.put(MediaStore.MediaColumns.IS_PENDING, 0);
|
||||||
|
}
|
||||||
|
values.put(MediaStore.MediaColumns.ORIENTATION, orientationDegrees);
|
||||||
|
int updatedRowCount = contentResolver.update(uri, values, null, null);
|
||||||
|
if (updatedRowCount > 0) {
|
||||||
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> {
|
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> {
|
||||||
Map<String, Object> newFields = new HashMap<>();
|
|
||||||
newFields.put("orientationDegrees", orientationDegrees);
|
|
||||||
callback.onSuccess(newFields);
|
callback.onSuccess(newFields);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Log.w(LOG_TAG, "failed to update fields in MediaStore for uri=" + uri);
|
||||||
|
callback.onSuccess(newFields);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,16 +255,29 @@ public abstract class ImageProvider {
|
||||||
// update fields in media store
|
// update fields in media store
|
||||||
@SuppressWarnings("SuspiciousNameCombination") int rotatedWidth = originalHeight;
|
@SuppressWarnings("SuspiciousNameCombination") int rotatedWidth = originalHeight;
|
||||||
@SuppressWarnings("SuspiciousNameCombination") int rotatedHeight = originalWidth;
|
@SuppressWarnings("SuspiciousNameCombination") int rotatedHeight = originalWidth;
|
||||||
|
Map<String, Object> newFields = new HashMap<>();
|
||||||
|
newFields.put("width", rotatedWidth);
|
||||||
|
newFields.put("height", rotatedHeight);
|
||||||
|
|
||||||
|
ContentResolver contentResolver = activity.getContentResolver();
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
|
// from Android Q, media store update needs to be flagged IS_PENDING first
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
values.put(MediaStore.MediaColumns.IS_PENDING, 1);
|
||||||
|
contentResolver.update(uri, values, null, null);
|
||||||
|
values.clear();
|
||||||
|
values.put(MediaStore.MediaColumns.IS_PENDING, 0);
|
||||||
|
}
|
||||||
values.put(MediaStore.MediaColumns.WIDTH, rotatedWidth);
|
values.put(MediaStore.MediaColumns.WIDTH, rotatedWidth);
|
||||||
values.put(MediaStore.MediaColumns.HEIGHT, rotatedHeight);
|
values.put(MediaStore.MediaColumns.HEIGHT, rotatedHeight);
|
||||||
if (activity.getContentResolver().update(uri, values, null, null) > 0) {
|
int updatedRowCount = contentResolver.update(uri, values, null, null);
|
||||||
|
if (updatedRowCount > 0) {
|
||||||
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> {
|
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> {
|
||||||
Map<String, Object> newFields = new HashMap<>();
|
|
||||||
newFields.put("width", rotatedWidth);
|
|
||||||
newFields.put("height", rotatedHeight);
|
|
||||||
callback.onSuccess(newFields);
|
callback.onSuccess(newFields);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Log.w(LOG_TAG, "failed to update fields in MediaStore for uri=" + uri);
|
||||||
|
callback.onSuccess(newFields);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.net.Uri;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import deckers.thibault.aves.model.ImageEntry;
|
import deckers.thibault.aves.model.ImageEntry;
|
||||||
|
@ -15,14 +14,12 @@ import deckers.thibault.aves.utils.Env;
|
||||||
import deckers.thibault.aves.utils.PermissionManager;
|
import deckers.thibault.aves.utils.PermissionManager;
|
||||||
import deckers.thibault.aves.utils.StorageUtils;
|
import deckers.thibault.aves.utils.StorageUtils;
|
||||||
import deckers.thibault.aves.utils.Utils;
|
import deckers.thibault.aves.utils.Utils;
|
||||||
|
import io.flutter.plugin.common.EventChannel;
|
||||||
|
|
||||||
public class MediaStoreImageProvider extends ImageProvider {
|
public class MediaStoreImageProvider extends ImageProvider {
|
||||||
private static final String LOG_TAG = Utils.createLogTag(MediaStoreImageProvider.class);
|
private static final String LOG_TAG = Utils.createLogTag(MediaStoreImageProvider.class);
|
||||||
|
|
||||||
public static Uri FILES_URI = MediaStore.Files.getContentUri("external");
|
private static final String[] IMAGE_PROJECTION = {
|
||||||
|
|
||||||
private static final String[] PROJECTION = {
|
|
||||||
// image & video
|
|
||||||
MediaStore.MediaColumns._ID,
|
MediaStore.MediaColumns._ID,
|
||||||
MediaStore.MediaColumns.DATA,
|
MediaStore.MediaColumns.DATA,
|
||||||
MediaStore.MediaColumns.MIME_TYPE,
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
|
@ -30,38 +27,28 @@ public class MediaStoreImageProvider extends ImageProvider {
|
||||||
MediaStore.MediaColumns.TITLE,
|
MediaStore.MediaColumns.TITLE,
|
||||||
MediaStore.MediaColumns.WIDTH,
|
MediaStore.MediaColumns.WIDTH,
|
||||||
MediaStore.MediaColumns.HEIGHT,
|
MediaStore.MediaColumns.HEIGHT,
|
||||||
MediaStore.Images.Media.ORIENTATION,
|
MediaStore.MediaColumns.ORIENTATION,
|
||||||
MediaStore.MediaColumns.DATE_MODIFIED,
|
MediaStore.MediaColumns.DATE_MODIFIED,
|
||||||
MediaStore.Images.Media.DATE_TAKEN,
|
MediaStore.MediaColumns.DATE_TAKEN,
|
||||||
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
|
MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
|
||||||
// video only
|
|
||||||
MediaStore.Video.Media.DURATION,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String SELECTION = MediaStore.Files.FileColumns.MEDIA_TYPE + "=" + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
|
private static final String[] VIDEO_PROJECTION = Stream.of(IMAGE_PROJECTION, new String[]{
|
||||||
+ " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=" + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
|
MediaStore.MediaColumns.DURATION
|
||||||
|
}).flatMap(Stream::of).toArray(String[]::new);
|
||||||
|
|
||||||
|
public void fetchAll(Activity activity, EventChannel.EventSink entrySink) {
|
||||||
public Stream<ImageEntry> fetchAll(Activity activity) {
|
fetch(activity, entrySink, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION);
|
||||||
return fetch(activity, FILES_URI);
|
fetch(activity, entrySink, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, VIDEO_PROJECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<ImageEntry> fetch(final Activity activity, final Uri queryUri) {
|
private void fetch(final Activity activity, EventChannel.EventSink entrySink, final Uri contentUri, String[] projection) {
|
||||||
ArrayList<ImageEntry> entries = new ArrayList<>();
|
String orderBy = MediaStore.MediaColumns.DATE_TAKEN + " DESC";
|
||||||
|
|
||||||
// URI should refer to the "files" table, not to the "images" or "videos" one,
|
|
||||||
// as our projection includes a mix of columns from both
|
|
||||||
Uri filesUri = queryUri;
|
|
||||||
if (!FILES_URI.equals(queryUri)) {
|
|
||||||
String id = queryUri.getLastPathSegment();
|
|
||||||
filesUri = Uri.withAppendedPath(FILES_URI, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
String orderBy = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Cursor cursor = activity.getContentResolver().query(filesUri, PROJECTION, SELECTION, null, orderBy);
|
Cursor cursor = activity.getContentResolver().query(contentUri, projection, null, null, orderBy);
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
// image & video
|
||||||
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID);
|
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID);
|
||||||
int pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
|
int pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
|
||||||
int mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE);
|
int mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE);
|
||||||
|
@ -69,15 +56,16 @@ public class MediaStoreImageProvider extends ImageProvider {
|
||||||
int titleColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE);
|
int titleColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE);
|
||||||
int widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH);
|
int widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH);
|
||||||
int heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT);
|
int heightColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.HEIGHT);
|
||||||
int orientationColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.ORIENTATION);
|
int orientationColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION);
|
||||||
int dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
|
int dateModifiedColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
|
||||||
int dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN);
|
int dateTakenColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_TAKEN);
|
||||||
int bucketDisplayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
|
int bucketDisplayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME);
|
||||||
int durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
|
// video only
|
||||||
|
int durationColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION);
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
long contentId = cursor.getLong(idColumn);
|
long contentId = cursor.getLong(idColumn);
|
||||||
Uri itemUri = ContentUris.withAppendedId(FILES_URI, contentId);
|
Uri itemUri = ContentUris.withAppendedId(contentUri, contentId);
|
||||||
ImageEntry imageEntry = new ImageEntry(
|
ImageEntry imageEntry = new ImageEntry(
|
||||||
itemUri,
|
itemUri,
|
||||||
cursor.getString(pathColumn),
|
cursor.getString(pathColumn),
|
||||||
|
@ -91,28 +79,34 @@ public class MediaStoreImageProvider extends ImageProvider {
|
||||||
cursor.getLong(dateModifiedColumn),
|
cursor.getLong(dateModifiedColumn),
|
||||||
cursor.getLong(dateTakenColumn),
|
cursor.getLong(dateTakenColumn),
|
||||||
cursor.getString(bucketDisplayNameColumn),
|
cursor.getString(bucketDisplayNameColumn),
|
||||||
cursor.getLong(durationColumn)
|
durationColumn != -1 ? cursor.getLong(durationColumn) : 0
|
||||||
);
|
);
|
||||||
|
// TODO TLAD sanitize mimeType
|
||||||
|
// problem: some images were added as image/jpeg, but they're actually image/png
|
||||||
|
// possible solution:
|
||||||
|
// 1) check that MediaStore mimeType matches expected mimeType from file path extension
|
||||||
|
// 2) extract actual mimeType with metadata-extractor
|
||||||
|
// 3) update MediaStore
|
||||||
if (imageEntry.getWidth() > 0) {
|
if (imageEntry.getWidth() > 0) {
|
||||||
entries.add(imageEntry);
|
// TODO TLAD avoid creating ImageEntry to convert it right after
|
||||||
// } else {
|
entrySink.success(ImageEntry.toMap(imageEntry));
|
||||||
// // some images are incorrectly registered in the MediaStore,
|
// } else {
|
||||||
// // they are valid but miss some attributes, such as width, height, orientation
|
// // some images are incorrectly registered in the MediaStore,
|
||||||
// try {
|
// // they are valid but miss some attributes, such as width, height, orientation
|
||||||
// imageEntry.fixMissingWidthHeightOrientation(activity);
|
// try {
|
||||||
// entries.add(imageEntry);
|
// imageEntry.fixMissingWidthHeightOrientation(activity);
|
||||||
// } catch (IOException e) {
|
// entrySink.success(imageEntry);
|
||||||
// // this is probably not a real image, like "/storage/emulated/0", so we skip it
|
// } catch (IOException e) {
|
||||||
// Log.w(LOG_TAG, "failed to compute dimensions of imageEntry=" + imageEntry);
|
// // this is probably not a real image, like "/storage/emulated/0", so we skip it
|
||||||
// }
|
// Log.w(LOG_TAG, "failed to compute dimensions of imageEntry=" + imageEntry);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(LOG_TAG, "failed to get entries", e);
|
Log.e(LOG_TAG, "failed to get entries", e);
|
||||||
}
|
}
|
||||||
return entries.stream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,4 +139,4 @@ public class MediaStoreImageProvider extends ImageProvider {
|
||||||
|
|
||||||
callback.onFailure();
|
callback.onFailure();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:aves/model/image_file_service.dart';
|
import 'package:aves/model/image_file_service.dart';
|
||||||
import 'package:aves/model/image_metadata.dart';
|
import 'package:aves/model/image_metadata.dart';
|
||||||
import 'package:aves/model/metadata_service.dart';
|
import 'package:aves/model/metadata_service.dart';
|
||||||
import 'package:aves/utils/change_notifier.dart';
|
import 'package:aves/utils/change_notifier.dart';
|
||||||
import 'package:aves/utils/time_utils.dart';
|
import 'package:aves/utils/time_utils.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoder/geocoder.dart';
|
import 'package:geocoder/geocoder.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
@ -228,6 +231,9 @@ class ImageEntry {
|
||||||
if (height is int) this.height = height;
|
if (height is int) this.height = height;
|
||||||
final orientationDegrees = newFields['orientationDegrees'];
|
final orientationDegrees = newFields['orientationDegrees'];
|
||||||
if (orientationDegrees is int) this.orientationDegrees = orientationDegrees;
|
if (orientationDegrees is int) this.orientationDegrees = orientationDegrees;
|
||||||
|
|
||||||
|
// TODO TLAD move cache eviction out of ImageEntry and into ImagePage together with `imageChangeNotifier` handling
|
||||||
|
await FileImage(File(this.path)).evict();
|
||||||
imageChangeNotifier.notifyListeners();
|
imageChangeNotifier.notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue