fixed crash when relaunching destroyed activity + minor fixes

This commit is contained in:
Thibault Deckers 2020-10-19 15:33:01 +09:00
parent c1e27d643d
commit f18befe486
7 changed files with 37 additions and 21 deletions

View file

@ -1,7 +1,6 @@
package deckers.thibault.aves.channel.streams;
import android.app.Activity;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
@ -21,6 +20,7 @@ import java.util.Map;
import deckers.thibault.aves.decoder.VideoThumbnail;
import deckers.thibault.aves.utils.BitmapUtils;
import deckers.thibault.aves.utils.MimeTypes;
import deckers.thibault.aves.utils.StorageUtils;
import io.flutter.plugin.common.EventChannel;
public class ImageByteStreamHandler implements EventChannel.StreamHandler {
@ -137,8 +137,7 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
Glide.with(activity).clear(target);
}
} else {
ContentResolver cr = activity.getContentResolver();
try (InputStream is = cr.openInputStream(uri)) {
try (InputStream is = StorageUtils.openInputStream(activity, uri)) {
if (is != null) {
streamBytes(is);
} else {

View file

@ -4,7 +4,10 @@ import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
class IntentStreamHandler : EventChannel.StreamHandler {
private lateinit var eventSink: EventSink
// cannot use `lateinit` because we cannot guarantee
// its initialization in `onListen` at the right time
// e.g. when resuming the app after the activity got destroyed
private var eventSink: EventSink? = null
override fun onListen(arguments: Any?, eventSink: EventSink) {
this.eventSink = eventSink
@ -13,6 +16,6 @@ class IntentStreamHandler : EventChannel.StreamHandler {
override fun onCancel(arguments: Any?) {}
fun notifyNewIntent() {
eventSink.success(true)
eventSink?.success(true)
}
}

View file

@ -257,9 +257,10 @@ object StorageUtils {
@JvmStatic
fun getDocumentFile(context: Context, anyPath: String, mediaUri: Uri): DocumentFileCompat? {
try {
if (requireAccessPermission(anyPath)) {
// need a document URI (not a media content URI) to open a `DocumentFile` output stream
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isMediaStoreContentUri(mediaUri)) {
// cleanest API to get it
val docUri = MediaStore.getDocumentUri(context, mediaUri)
if (docUri != null) {
@ -271,6 +272,10 @@ object StorageUtils {
}
// good old `File`
return DocumentFileCompat.fromFile(File(anyPath))
} catch (e: SecurityException) {
Log.w(LOG_TAG, "failed to get document file from mediaUri=$mediaUri", e)
}
return null
}
// returns the directory `DocumentFile` (from tree URI when scoped storage is required, `File` otherwise)
@ -368,6 +373,7 @@ object StorageUtils {
return ContentResolver.SCHEME_CONTENT.equals(uri.scheme, ignoreCase = true) && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true)
}
@JvmStatic
fun openInputStream(context: Context, uri: Uri): InputStream? {
var effectiveUri = uri
// we get a permission denial if we require original from a provider other than the media store
@ -380,6 +386,9 @@ object StorageUtils {
} catch (e: FileNotFoundException) {
Log.w(LOG_TAG, "failed to find file at uri=$effectiveUri")
null
} catch (e: SecurityException) {
Log.w(LOG_TAG, "failed to open file at uri=$effectiveUri", e)
null
}
}

View file

@ -18,6 +18,8 @@ class Constants {
offset: Offset(0.5, 1.0),
);
static const String unknown = 'unknown';
static const pointNemo = Tuple2(-48.876667, -123.393333);
static const int infoGroupMaxValueLength = 140;

View file

@ -6,6 +6,7 @@ import 'package:aves/model/filters/tag.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/mime_types.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/file_utils.dart';
import 'package:aves/widgets/common/aves_filter_chip.dart';
import 'package:aves/widgets/fullscreen/info/info_page.dart';
@ -28,7 +29,7 @@ class BasicSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final date = entry.bestDate;
final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : '?';
final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : Constants.unknown;
final showMegaPixels = entry.isPhoto && entry.megaPixels != null && entry.megaPixels > 0;
final resolutionText = '${entry.width ?? '?'} × ${entry.height ?? '?'}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}';
@ -36,12 +37,12 @@ class BasicSection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InfoRowGroup({
'Title': entry.bestTitle ?? '?',
'Title': entry.bestTitle ?? Constants.unknown,
'Date': dateText,
if (entry.isVideo) ..._buildVideoRows(),
if (!entry.isSvg) 'Resolution': resolutionText,
'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : '?',
'URI': entry.uri ?? '?',
'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : Constants.unknown,
'URI': entry.uri ?? Constants.unknown,
if (entry.path != null) 'Path': entry.path,
}),
_buildChips(),

View file

@ -65,6 +65,8 @@ class InfoPageState extends State<InfoPage> {
return ValueListenableBuilder<ImageEntry>(
valueListenable: widget.entryNotifier,
builder: (context, entry, child) {
if (entry == null) return SizedBox.shrink();
final locationAtTop = split && entry.hasGps;
final locationSection = LocationSection(
collection: collection,

View file

@ -228,7 +228,7 @@ class _DateRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final date = entry.bestDate;
final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : '?';
final dateText = date != null ? '${DateFormat.yMMMd().format(date)}${DateFormat.Hm().format(date)}' : Constants.unknown;
final resolution = '${entry.width ?? '?'} × ${entry.height ?? '?'}';
return Row(
children: [