Add image provider backed by content uri

This commit is contained in:
Ming Ming 2022-05-04 18:29:58 +08:00
parent a65c8d824e
commit c6e6b99128
6 changed files with 161 additions and 0 deletions

View file

@ -0,0 +1,64 @@
import 'dart:ui' as ui show Codec;
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
class ContentUriImage extends ImageProvider<ContentUriImage>
with EquatableMixin {
/// Creates an object that decodes a content Uri as an image.
const ContentUriImage(
this.uri, {
this.scale = 1.0,
});
@override
obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<ContentUriImage>(this);
}
@override
load(ContentUriImage key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: key.scale,
debugLabel: key.uri,
informationCollector: () => <DiagnosticsNode>[
ErrorDescription("Content uri: $uri"),
],
);
}
Future<ui.Codec> _loadAsync(
ContentUriImage key, DecoderCallback decode) async {
assert(key == this);
final bytes = await ContentUri.readUri(uri);
if (bytes.lengthInBytes == 0) {
// The file may become available later.
PaintingBinding.instance!.imageCache!.evict(key);
throw StateError("$uri is empty and cannot be loaded as an image.");
}
return decode(bytes);
}
@override
get props => [
uri,
scale,
];
@override
toString() => "${objectRuntimeType(this, "ContentUriImage")} {"
"uri: $uri, "
"scale: $scale, "
"}";
final String uri;
/// The scale to place in the [ImageInfo] object of the image.
final double scale;
}

View file

@ -0,0 +1,45 @@
package com.nkming.nc_photos.plugin
import android.content.Context
import android.net.Uri
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.FileNotFoundException
class ContentUriChannelHandler(context: Context) :
MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method"
private const val TAG = "ContentUriChannelHandler"
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"readUri" -> {
try {
readUri(call.argument("uri")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> result.notImplemented()
}
}
private fun readUri(uri: String, result: MethodChannel.Result) {
val uriTyped = Uri.parse(uri)
try {
val bytes =
context.contentResolver.openInputStream(uriTyped)!!.use {
it.readBytes()
}
result.success(bytes)
} catch (e: FileNotFoundException) {
result.error("fileNotFoundException", e.toString(), null)
}
}
private val context = context
}

View file

@ -62,6 +62,14 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
flutterPluginBinding.applicationContext
)
)
contentUriMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ContentUriChannelHandler.METHOD_CHANNEL
)
contentUriMethodChannel.setMethodCallHandler(
ContentUriChannelHandler(flutterPluginBinding.applicationContext)
)
}
override fun onDetachedFromEngine(
@ -74,6 +82,7 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
nativeEventMethodChannel.setMethodCallHandler(null)
mediaStoreMethodChannel.setMethodCallHandler(null)
imageProcessorMethodChannel.setMethodCallHandler(null)
contentUriMethodChannel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
@ -100,6 +109,7 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware {
private lateinit var nativeEventMethodChannel: MethodChannel
private lateinit var mediaStoreMethodChannel: MethodChannel
private lateinit var imageProcessorMethodChannel: MethodChannel
private lateinit var contentUriMethodChannel: MethodChannel
private lateinit var lockChannelHandler: LockChannelHandler
private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler

View file

@ -1,5 +1,6 @@
library nc_photos_plugin;
export 'src/content_uri.dart';
export 'src/exception.dart';
export 'src/image_processor.dart';
export 'src/lock.dart';

View file

@ -0,0 +1,26 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:nc_photos_plugin/src/exception.dart';
import 'package:nc_photos_plugin/src/k.dart' as k;
class ContentUri {
static Future<Uint8List> readUri(String uri) async {
try {
return await _methodChannel.invokeMethod("readUri", <String, dynamic>{
"uri": uri,
});
} on PlatformException catch (e) {
if (e.code == _exceptionFileNotFound) {
throw const FileNotFoundException();
} else {
rethrow;
}
}
}
static const _methodChannel = MethodChannel("${k.libId}/content_uri_method");
static const _exceptionFileNotFound = "fileNotFoundException";
}

View file

@ -1,3 +1,18 @@
class FileNotFoundException implements Exception {
const FileNotFoundException([this.message]);
@override
toString() {
if (message == null) {
return "FileNotFoundException";
} else {
return "FileNotFoundException: $message";
}
}
final dynamic message;
}
/// Platform permission is not granted by user
class PermissionException implements Exception {
const PermissionException([this.message]);