Adds Android implementation
This commit is contained in:
parent
1916a9acce
commit
c4d80c5d7c
|
@ -0,0 +1,83 @@
|
|||
package com.mr.flutter.plugin.filepicker;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FileInfo {
|
||||
|
||||
final Uri uri;
|
||||
final String path;
|
||||
final String name;
|
||||
final int size;
|
||||
final byte[] bytes;
|
||||
final boolean isDirectory;
|
||||
|
||||
public FileInfo(Uri uri, String path, String name, int size, byte[] bytes, boolean isDirectory) {
|
||||
this.uri = uri;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.size = size;
|
||||
this.bytes = bytes;
|
||||
this.isDirectory = isDirectory;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Uri uri;
|
||||
private String path;
|
||||
private String name;
|
||||
private int size;
|
||||
private byte[] bytes;
|
||||
private boolean isDirectory;
|
||||
|
||||
public Builder withUri(Uri uri){
|
||||
this.uri = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPath(String path){
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withName(String name){
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSize(int size){
|
||||
this.size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withData(byte[] bytes){
|
||||
this.bytes = bytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDirectory(String directory){
|
||||
this.path = directory;
|
||||
this.isDirectory = directory != null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileInfo build() {
|
||||
return new FileInfo(this.uri, this.path, this.name, this.size, this.bytes, this.isDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public HashMap<String, Object> toMap() {
|
||||
final HashMap<String, Object> data = new HashMap<>();
|
||||
data.put("uri", uri.toString());
|
||||
data.put("path", path);
|
||||
data.put("name", name);
|
||||
data.put("size", size);
|
||||
data.put("bytes", bytes);
|
||||
data.put("isDirectory", isDirectory);
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -17,8 +17,11 @@ import androidx.annotation.VisibleForTesting;
|
|||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
@ -82,49 +85,42 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
|
|||
@Override
|
||||
public void run() {
|
||||
if (data != null) {
|
||||
final ArrayList<FileInfo> files = new ArrayList<>();
|
||||
|
||||
if (data.getClipData() != null) {
|
||||
final int count = data.getClipData().getItemCount();
|
||||
int currentItem = 0;
|
||||
final ArrayList<String> paths = new ArrayList<>();
|
||||
while (currentItem < count) {
|
||||
final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
|
||||
String path;
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
path = FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, currentUri);
|
||||
} else {
|
||||
path = FileUtils.getPath(currentUri, FilePickerDelegate.this.activity);
|
||||
if (path == null) {
|
||||
path = FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, currentUri);
|
||||
}
|
||||
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, currentUri);
|
||||
|
||||
if(file != null) {
|
||||
files.add(file);
|
||||
Log.d(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath());
|
||||
}
|
||||
paths.add(path);
|
||||
Log.i(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath());
|
||||
currentItem++;
|
||||
}
|
||||
|
||||
finishWithSuccess(paths);
|
||||
|
||||
finishWithSuccess(files);
|
||||
} else if (data.getData() != null) {
|
||||
Uri uri = data.getData();
|
||||
String fullPath;
|
||||
|
||||
if (type.equals("dir") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
|
||||
}
|
||||
|
||||
Log.i(FilePickerDelegate.TAG, "[SingleFilePick] File URI:" + uri.toString());
|
||||
Log.d(FilePickerDelegate.TAG, "[SingleFilePick] File URI:" + uri.toString());
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
fullPath = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
|
||||
} else {
|
||||
fullPath = FileUtils.getPath(uri, FilePickerDelegate.this.activity);
|
||||
if (fullPath == null) {
|
||||
fullPath = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
|
||||
final FileInfo file = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.openFileStream(FilePickerDelegate.this.activity, uri);
|
||||
if(file != null) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (fullPath != null) {
|
||||
Log.i(FilePickerDelegate.TAG, "Absolute file path:" + fullPath);
|
||||
finishWithSuccess(Arrays.asList(fullPath));
|
||||
if (!files.isEmpty()) {
|
||||
Log.d(FilePickerDelegate.TAG, "File path:" + files.toString());
|
||||
finishWithSuccess(files);
|
||||
} else {
|
||||
finishWithError("unknown_path", "Failed to retrieve path.");
|
||||
}
|
||||
|
@ -137,7 +133,6 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
|
|||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
return true;
|
||||
|
||||
} else if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_CANCELED) {
|
||||
|
@ -238,13 +233,20 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
|
|||
this.startFileExplorer();
|
||||
}
|
||||
|
||||
private void finishWithSuccess(final Object data) {
|
||||
private void finishWithSuccess(final ArrayList<FileInfo> files) {
|
||||
if (eventSink != null) {
|
||||
this.dispatchEventStatus(false);
|
||||
}
|
||||
|
||||
// Temporary fix, remove this null-check after Flutter Engine 1.14 has landed on stable
|
||||
if (this.pendingResult != null) {
|
||||
|
||||
final ArrayList<HashMap<String, Object>> data = new ArrayList<>();
|
||||
|
||||
for(FileInfo file : files) {
|
||||
data.add(file.toMap());
|
||||
}
|
||||
|
||||
this.pendingResult.success(data);
|
||||
this.clearPendingResult();
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import android.webkit.MimeTypeMap;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -236,19 +236,21 @@ public class FileUtils {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static String getUriFromRemote(final Context context, final Uri uri) {
|
||||
public static FileInfo openFileStream(final Context context, final Uri uri) {
|
||||
|
||||
Log.i(TAG, "Caching file from remote/external URI");
|
||||
Log.i(TAG, "Caching from URI: " + uri.toString());
|
||||
FileOutputStream fos = null;
|
||||
final FileInfo.Builder fileInfo = new FileInfo.Builder();
|
||||
final String fileName = FileUtils.getFileName(uri, context);
|
||||
final String externalFile = context.getCacheDir().getAbsolutePath() + "/file_picker/" + (fileName != null ? fileName : new Random().nextInt(100000));
|
||||
final String path = context.getCacheDir().getAbsolutePath() + "/file_picker/" + (fileName != null ? fileName : new Random().nextInt(100000));
|
||||
|
||||
new File(externalFile).getParentFile().mkdirs();
|
||||
final File file = new File(path);
|
||||
file.getParentFile().mkdirs();
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(externalFile);
|
||||
fos = new FileOutputStream(path);
|
||||
try {
|
||||
final BufferedOutputStream out = new BufferedOutputStream(fos);
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
final InputStream in = context.getContentResolver().openInputStream(uri);
|
||||
|
||||
final byte[] buffer = new byte[8192];
|
||||
|
@ -258,6 +260,8 @@ public class FileUtils {
|
|||
out.write(buffer, 0, len);
|
||||
}
|
||||
|
||||
fileInfo.withData(out.toByteArray());
|
||||
out.writeTo(fos);
|
||||
out.flush();
|
||||
} finally {
|
||||
fos.getFD().sync();
|
||||
|
@ -273,28 +277,50 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
Log.i(TAG, "File loaded and cached at:" + externalFile);
|
||||
return externalFile;
|
||||
Log.d(TAG, "File loaded and cached at:" + path);
|
||||
|
||||
fileInfo
|
||||
.withPath(path)
|
||||
.withName(fileName)
|
||||
.withSize(Integer.parseInt(String.valueOf(file.length()/1024)))
|
||||
.withUri(uri);
|
||||
|
||||
return fileInfo.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
|
||||
if (treeUri == null) return null;
|
||||
public static FileInfo getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
|
||||
if (treeUri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con);
|
||||
if (volumePath == null) return File.separator;
|
||||
FileInfo.Builder fileInfo = new FileInfo.Builder();
|
||||
|
||||
fileInfo.withUri(treeUri);
|
||||
|
||||
if (volumePath == null) {
|
||||
return fileInfo.withDirectory(File.separator).build();
|
||||
}
|
||||
|
||||
if (volumePath.endsWith(File.separator))
|
||||
volumePath = volumePath.substring(0, volumePath.length() - 1);
|
||||
|
||||
String documentPath = getDocumentPathFromTreeUri(treeUri);
|
||||
|
||||
if (documentPath.endsWith(File.separator))
|
||||
documentPath = documentPath.substring(0, documentPath.length() - 1);
|
||||
|
||||
if (documentPath.length() > 0) {
|
||||
if (documentPath.startsWith(File.separator))
|
||||
return volumePath + documentPath;
|
||||
else
|
||||
return volumePath + File.separator + documentPath;
|
||||
} else return volumePath;
|
||||
if (documentPath.startsWith(File.separator)) {
|
||||
return fileInfo.withDirectory(volumePath + documentPath).build();
|
||||
}
|
||||
else {
|
||||
return fileInfo.withDirectory(volumePath + File.separator + documentPath).build();
|
||||
}
|
||||
} else {
|
||||
return fileInfo.withDirectory(volumePath).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
|
@ -64,7 +64,7 @@ class FilePickerIO extends FilePicker {
|
|||
);
|
||||
}
|
||||
|
||||
final List<String> result = await _channel.invokeListMethod(type, {
|
||||
final List<Map> result = await _channel.invokeListMethod(type, {
|
||||
'allowMultipleSelection': allowMultipleSelection,
|
||||
'allowedExtensions': allowedExtensions,
|
||||
'allowCompression': allowCompression,
|
||||
|
@ -74,7 +74,7 @@ class FilePickerIO extends FilePicker {
|
|||
return null;
|
||||
}
|
||||
|
||||
return FilePickerResult(result.map((file) => PlatformFile(name: file.split('/').last, path: file)).toList());
|
||||
return FilePickerResult(result.map((file) => PlatformFile.fromMap(file)).toList());
|
||||
} on PlatformException catch (e) {
|
||||
print('[$_tag] Platform exception: $e');
|
||||
rethrow;
|
||||
|
|
|
@ -6,32 +6,42 @@ class PlatformFile {
|
|||
this.uri,
|
||||
this.name,
|
||||
this.bytes,
|
||||
this.size,
|
||||
this.isDirectory = false,
|
||||
});
|
||||
|
||||
/// The absolute path for this file instance.
|
||||
///
|
||||
/// Typically whis will reflect a copy cached file and not the original source,
|
||||
/// also, it's not guaranteed that this path is always available as some files
|
||||
/// can be protected by OS.
|
||||
///
|
||||
/// Available on IO only. On Web is always `null`.
|
||||
PlatformFile.fromMap(Map data)
|
||||
: this.path = data['path'],
|
||||
this.uri = data['uri'],
|
||||
this.name = data['name'],
|
||||
this.bytes = data['bytes'],
|
||||
this.size = data['size'],
|
||||
this.isDirectory = data['isDirectory'];
|
||||
|
||||
/// The absolute path for a cached copy of this file.
|
||||
/// If you want to access the original file identifier use [uri] property instead.
|
||||
final String path;
|
||||
|
||||
/// The URI (Universal Resource Identifier) for this file.
|
||||
///
|
||||
/// This is the original file resource identifier and can be used to
|
||||
/// This is the identifier of original resource and can be used to
|
||||
/// manipulate the original file (read, write, delete).
|
||||
///
|
||||
/// Available on IO only. On Web is always `null`.
|
||||
/// Android: it can be either content:// or file:// url.
|
||||
/// iOS: a file:// URL below a document provider (like iCloud).
|
||||
/// Web: Not supported, will be always `null`.
|
||||
final String uri;
|
||||
|
||||
/// File name including its extension.
|
||||
final String name;
|
||||
|
||||
/// Byte data for this file.
|
||||
/// Byte data for this file. Particurlarly useful if you want to manipulate its data
|
||||
/// or easily upload to somewhere else.
|
||||
final Uint8List bytes;
|
||||
|
||||
/// The file size in KB.
|
||||
final int size;
|
||||
|
||||
/// Whether this file references a directory or not.
|
||||
final bool isDirectory;
|
||||
|
||||
|
|
Loading…
Reference in New Issue