Adds iOS implementation, removes URI and isDirectory and adds withData optional property

This commit is contained in:
Miguel Ruivo 2020-09-11 18:01:34 +01:00
parent ea601246fd
commit 1c0e471458
16 changed files with 150 additions and 99 deletions

View File

@ -6,38 +6,24 @@ import java.util.HashMap;
public class FileInfo {
final Uri uri;
final String path;
final String name;
final int size;
final byte[] bytes;
final long lastModified;
final boolean isDirectory;
public FileInfo(Uri uri, String path, String name, int size, byte[] bytes, boolean isDirectory, long lastModified) {
this.uri = uri;
public FileInfo(String path, String name, int size, byte[] bytes) {
this.path = path;
this.name = name;
this.size = size;
this.bytes = bytes;
this.lastModified = lastModified;
this.isDirectory = isDirectory;
}
public static class Builder {
private Uri uri;
private String path;
private String name;
private int size;
private long lastModified;
private byte[] bytes;
private boolean isDirectory;
public Builder withUri(Uri uri){
this.uri = uri;
return this;
}
public Builder withPath(String path){
this.path = path;
@ -59,32 +45,18 @@ public class FileInfo {
return this;
}
public Builder withDirectory(String path){
this.path = path;
this.isDirectory = path != null;
return this;
}
public Builder lastModifiedAt(long timeStamp){
this.lastModified = timeStamp;
return this;
}
public FileInfo build() {
return new FileInfo(this.uri, this.path, this.name, this.size, this.bytes, this.isDirectory, this.lastModified);
return new FileInfo(this.path, this.name, this.size, this.bytes);
}
}
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);
data.put("lastModified", lastModified);
return data;
}
}

View File

@ -17,10 +17,7 @@ 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;
@ -36,6 +33,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
private final PermissionManager permissionManager;
private MethodChannel.Result pendingResult;
private boolean isMultipleSelection = false;
private boolean loadDataToMemory = false;
private String type;
private String[] allowedExtensions;
private EventChannel.EventSink eventSink;
@ -92,7 +90,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
int currentItem = 0;
while (currentItem < count) {
final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, currentUri);
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, currentUri, loadDataToMemory);
if(file != null) {
files.add(file);
@ -121,7 +119,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
return;
}
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, uri);
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, uri, loadDataToMemory);
if(file != null) {
files.add(file);
@ -223,7 +221,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
}
@SuppressWarnings("deprecation")
public void startFileExplorer(final String type, final boolean isMultipleSelection, final String[] allowedExtensions, final MethodChannel.Result result) {
public void startFileExplorer(final String type, final boolean isMultipleSelection, final boolean withData, final String[] allowedExtensions, final MethodChannel.Result result) {
if (!this.setPendingMethodCallAndResult(result)) {
finishWithAlreadyActiveError(result);
@ -232,6 +230,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
this.type = type;
this.isMultipleSelection = isMultipleSelection;
this.loadDataToMemory = withData;
this.allowedExtensions = allowedExtensions;
if (!this.permissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) {
@ -242,6 +241,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
this.startFileExplorer();
}
@SuppressWarnings("unchecked")
private void finishWithSuccess(Object data) {
if (eventSink != null) {
this.dispatchEventStatus(false);

View File

@ -112,6 +112,7 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
private MethodChannel channel;
private static String fileType;
private static boolean isMultipleSelection = false;
private static boolean withData = false;
/**
* Plugin registration.
@ -160,13 +161,14 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
result.notImplemented();
} else if (fileType != "dir") {
isMultipleSelection = (boolean) arguments.get("allowMultipleSelection");
withData = (boolean) arguments.get("withData");
allowedExtensions = FileUtils.getMimeTypes((ArrayList<String>) arguments.get("allowedExtensions"));
}
if (fileType == "custom" && (allowedExtensions == null || allowedExtensions.length == 0)) {
result.error(TAG, "Unsupported filter. Make sure that you are only using the extension without the dot, (ie., jpg instead of .jpg). This could also have happened because you are using an unsupported file extension. If the problem persists, you may want to consider using FileType.all instead.", null);
} else {
this.delegate.startFileExplorer(fileType, isMultipleSelection, allowedExtensions, result);
this.delegate.startFileExplorer(fileType, isMultipleSelection, withData, allowedExtensions, result);
}
}

View File

@ -116,7 +116,7 @@ public class FileUtils {
return true;
}
public static FileInfo openFileStream(final Context context, final Uri uri) {
public static FileInfo openFileStream(final Context context, final Uri uri, boolean withData) {
Log.i(TAG, "Caching from URI: " + uri.toString());
FileOutputStream fos = null;
@ -126,7 +126,7 @@ public class FileUtils {
final File file = new File(path);
if(file.exists()) {
if(file.exists() && withData) {
int size = (int) file.length();
byte[] bytes = new byte[size];
@ -156,7 +156,9 @@ public class FileUtils {
out.write(buffer, 0, len);
}
fileInfo.withData(out.toByteArray());
if(withData) {
fileInfo.withData(out.toByteArray());
}
out.writeTo(fos);
out.flush();
} finally {
@ -177,11 +179,9 @@ public class FileUtils {
Log.d(TAG, "File loaded and cached at:" + path);
fileInfo
.lastModifiedAt(file.lastModified())
.withPath(path)
.withName(fileName)
.withSize(Integer.parseInt(String.valueOf(file.length()/1024)))
.withUri(uri);
.withSize(Integer.parseInt(String.valueOf(file.length()/1024)));
return fileInfo.build();
}
@ -195,8 +195,6 @@ public class FileUtils {
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con);
FileInfo.Builder fileInfo = new FileInfo.Builder();
fileInfo.withUri(treeUri);
if (volumePath == null) {
return File.separator;
}

View File

@ -17,7 +17,7 @@ allprojects {
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
options.compilerArgs << "-Xlint:deprecation"
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
}

View File

@ -164,7 +164,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1010;
LastUpgradeCheck = 1170;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@ -176,10 +176,9 @@
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
@ -416,6 +415,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@ -443,6 +443,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1170"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -0,0 +1,21 @@
//
// FileInfo.h
// file_picker
//
// Created by Miguel Ruivo on 11/09/2020.
//
@interface FileInfo : NSObject
@property (nonatomic, strong) NSString * path;
@property (nonatomic, strong) NSString * name;
@property (nonatomic, strong) NSNumber * size;
@property (nonatomic, strong) NSData * bytes;
@property (nonatomic, strong) NSNumber * isDirectory;
- (instancetype) initWithPath: (NSString *)path andName: (NSString *)name andSize: (NSNumber *) size andData:(NSData*) data;
- (NSDictionary *) toData;
@end

View File

@ -0,0 +1,34 @@
//
// FileInfo.m
// file_picker
//
// Created by Miguel Ruivo on 11/09/2020.
//
#import "FileInfo.h"
@implementation FileInfo
- (instancetype) initWithPath: (NSString *)path andName: (NSString *)name andSize: (NSNumber *) size andData:(NSData*) data {
self = [super init];
if (self) {
self.path = path;
self.name = name;
self.size = size;
self.bytes = data;
}
return self;
}
- (NSDictionary *)toData {
NSMutableDictionary * data = [[NSMutableDictionary alloc] init];
[data setValue:self.path forKey:@"path"];
[data setValue:self.name forKey:@"name"];
[data setValue:self.size forKey:@"size"];
[data setValue:self.bytes forKey:@"bytes"];
return data;
}
@end

View File

@ -13,6 +13,7 @@
@property (nonatomic) UIDocumentInteractionController *interactionController;
@property (nonatomic) MPMediaPickerController *audioPickerController;
@property (nonatomic) NSArray<NSString *> * allowedExtensions;
@property (nonatomic) BOOL loadDataToMemory;
@end
@implementation FilePickerPlugin
@ -76,6 +77,8 @@
NSDictionary * arguments = call.arguments;
BOOL isMultiplePick = ((NSNumber*)[arguments valueForKey:@"allowMultipleSelection"]).boolValue;
self.loadDataToMemory = ((NSNumber*)[arguments valueForKey:@"withData"]).boolValue;
if([call.method isEqualToString:@"any"] || [call.method containsString:@"custom"]) {
self.allowedExtensions = [FileUtils resolveType:call.method withAllowedExtensions: [arguments valueForKey:@"allowedExtensions"]];
if(self.allowedExtensions == nil) {
@ -89,7 +92,7 @@
} else if([call.method isEqualToString:@"video"] || [call.method isEqualToString:@"image"] || [call.method isEqualToString:@"media"]) {
[self resolvePickMedia:[FileUtils resolveMediaType:call.method] withMultiPick:isMultiplePick withCompressionAllowed:[arguments valueForKey:@"allowCompression"]];
} else if([call.method isEqualToString:@"audio"]) {
[self resolvePickAudio];
[self resolvePickAudioWithMultiPick: isMultiplePick];
} else {
result(FlutterMethodNotImplemented);
_result = nil;
@ -223,38 +226,40 @@
// Did select
[dkImagePickerController setDidSelectAssets:^(NSArray<DKAsset*> * __nonnull DKAssets) {
NSMutableArray<NSString*>* paths = [[NSMutableArray<NSString*> alloc] init];
NSMutableArray<NSURL*>* paths = [[NSMutableArray<NSURL*> alloc] init];
for(DKAsset * asset in DKAssets){
[paths addObject:asset.localTemporaryPath.path];
[paths addObject:asset.localTemporaryPath.absoluteURL];
}
self->_result([paths count] > 0 ? paths : nil);
self->_result = nil;
[self handleResult: paths];
}];
[_viewController presentViewController:dkImagePickerController animated:YES completion:nil];
}
- (void) resolvePickAudio {
- (void) resolvePickAudioWithMultiPick:(BOOL)isMultiPick {
self.audioPickerController = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];
self.audioPickerController.delegate = self;
self.audioPickerController.showsCloudItems = NO;
self.audioPickerController.allowsPickingMultipleItems = NO;
self.audioPickerController.showsCloudItems = YES;
self.audioPickerController.allowsPickingMultipleItems = isMultiPick;
self.audioPickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.viewController presentViewController:self.audioPickerController animated:YES completion:nil];
}
- (void) handleResult:(id) files {
_result([FileUtils resolveFileInfo: [files isKindOfClass: [NSArray class]] ? files : @[files] withData:self.loadDataToMemory]);
_result = nil;
}
#pragma mark - Delegates
// DocumentPicker delegate - iOS 10 only
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url{
[self.documentPickerController dismissViewControllerAnimated:YES completion:nil];
NSString * path = (NSString *)[url path];
_result(@[path]);
_result = nil;
[self handleResult:url];
}
// DocumentPicker delegate
@ -266,10 +271,14 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
}
[self.documentPickerController dismissViewControllerAnimated:YES completion:nil];
NSArray * result = [FileUtils resolvePath:urls];
_result(result);
_result = nil;
if(controller.documentPickerMode == UIDocumentPickerModeOpen) {
_result([urls objectAtIndex:0].path);
_result = nil;
return;
}
[self handleResult: urls];
}
@ -318,8 +327,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
return;
}
_result(@[[pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]]);
_result = nil;
[self handleResult: pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl];
}
@ -327,12 +335,16 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
- (void)mediaPicker: (MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
[mediaPicker dismissViewControllerAnimated:YES completion:NULL];
NSURL *url = [[[mediaItemCollection items] objectAtIndex:0] valueForKey:MPMediaItemPropertyAssetURL];
if(url == nil) {
NSMutableArray<NSURL *> * urls = [[NSMutableArray alloc] initWithCapacity:[mediaItemCollection items].count];
for(MPMediaItemCollection * item in [mediaItemCollection items]) {
[urls addObject: [item valueForKey:MPMediaItemPropertyAssetURL]];
}
if(urls.count == 0) {
Log(@"Couldn't retrieve the audio file path, either is not locally downloaded or the file is DRM protected.");
}
_result([url absoluteString]);
_result = nil;
[self handleResult:urls];
}
#pragma mark - Actions canceled

View File

@ -23,7 +23,7 @@ typedef NS_ENUM(NSInteger, MediaType) {
+ (BOOL) clearTemporaryFiles;
+ (NSArray<NSString*>*) resolveType:(NSString*)type withAllowedExtensions:(NSArray<NSString*>*)allowedExtensions;
+ (MediaType) resolveMediaType:(NSString*)type;
+ (NSArray*) resolvePath:(NSArray<NSURL *> *)urls;
+ (NSArray<NSDictionary*>*) resolveFileInfo:(NSArray<NSURL *> *)urls withData:(BOOL)loadData;
@end

View File

@ -6,11 +6,12 @@
//
#import "FileUtils.h"
#import "FileInfo.h"
@implementation FileUtils
+ (BOOL) clearTemporaryFiles {
NSString *tmpDirectory = NSTemporaryDirectory();
NSString *tmpDirectory = NSTemporaryDirectory();
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:tmpDirectory error:&error];
@ -75,16 +76,25 @@
}
}
+ (NSMutableArray*) resolvePath:(NSArray<NSURL *> *)urls{
NSString * uri;
NSMutableArray * paths = [[NSMutableArray alloc] init];
+ (NSArray<NSDictionary *> *)resolveFileInfo:(NSArray<NSURL *> *)urls withData: (BOOL)loadData {
for (NSURL *url in urls) {
uri = (NSString *)[url path];
[paths addObject:uri];
if(urls == nil) {
return nil;
}
return paths;
NSMutableArray * files = [[NSMutableArray alloc] initWithCapacity:urls.count];
for(NSURL * url in urls) {
NSString * path = (NSString *)[url path];
NSDictionary<NSFileAttributeKey, id> * fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
[files addObject: [[[FileInfo alloc] initWithPath: path
andName: [[path lastPathComponent] stringByDeletingPathExtension]
andSize: [NSNumber numberWithLongLong: [@(fileAttributes.fileSize) longLongValue] / 1024]
andData: loadData ? [NSData dataWithContentsOfFile:path options: 0 error:nil] : nil] toData]];
}
return files;
}
@end

View File

@ -55,6 +55,7 @@ abstract class FilePicker extends PlatformInterface {
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
bool allowMultiple = false,
bool withData,
}) async =>
throw UnimplementedError('pickFiles() has not been implemented.');

View File

@ -20,10 +20,18 @@ class FilePickerIO extends FilePicker {
FileType type = FileType.any,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
bool allowCompression = true,
bool allowMultiple = false,
bool withData = false,
}) =>
_getPath(type, allowMultiple, allowCompression, allowedExtensions, onFileLoading);
_getPath(
type,
allowMultiple,
allowCompression,
allowedExtensions,
onFileLoading,
withData,
);
@override
Future<bool> clearTemporaryFiles() async => _channel.invokeMethod<bool>('clear');
@ -47,6 +55,7 @@ class FilePickerIO extends FilePicker {
bool allowCompression,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
bool withData,
) async {
final String type = describeEnum(fileType);
if (type != 'custom' && (allowedExtensions?.isNotEmpty ?? false)) {
@ -65,6 +74,7 @@ class FilePickerIO extends FilePicker {
'allowMultipleSelection': allowMultipleSelection,
'allowedExtensions': allowedExtensions,
'allowCompression': allowCompression,
'withData': withData,
});
if (result == null) {

View File

@ -24,6 +24,7 @@ class FilePickerWeb extends FilePicker {
bool allowMultiple = false,
Function(FilePickerStatus) onFileLoading,
bool allowCompression,
bool withData = true,
}) async {
final Completer<List<PlatformFile>> filesCompleter = Completer<List<PlatformFile>>();
@ -48,7 +49,7 @@ class FilePickerWeb extends FilePicker {
name: uploadInput.value.replaceAll('\\', '/'),
path: uploadInput.value,
size: bytes.length ~/ 1024,
bytes: bytes,
bytes: withData ? bytes : null,
),
);

View File

@ -3,7 +3,6 @@ import 'dart:typed_data';
class PlatformFile {
const PlatformFile({
this.path,
this.uri,
this.name,
this.bytes,
this.size,
@ -12,28 +11,18 @@ class PlatformFile {
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.
/// The absolute path for a cached copy of this file. It can be used to create a
/// a file instance with a descriptor for the given path.
/// ```
/// final File myFile = File(platformFile.path);
/// ```
final String path;
/// The URI (Universal Resource Identifier) for this file.
///
/// This is the identifier of original resource and can be used to
/// manipulate the original file (read, write, delete).
///
/// 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;
@ -48,5 +37,5 @@ class PlatformFile {
final bool isDirectory;
/// File extension for this file.
String get extension => name?.split('/')?.last;
String get extension => path?.split('.')?.last;
}