diff --git a/README.md b/README.md index 05e99b8d..340a9544 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,18 @@ The file size of the document. _On Android some DocumentProviders may not provid The base64 encoded content of the picked file if the option `readContent` was set to `true`. + +#### [Android only] `DocumentPicker.pickDirectory(options)` + +Open a system directory picker. Returns a promise that resolves to the directory selected by user. + +### Result + +##### `uri`: + +The URI of the selected directory. Usually it will be a content URI. + + ### `DocumentPicker.types.*` `DocumentPicker.types.*` provides a few common types for use as `type` values, these types will use the correct format for each platform (MIME types on Android, UTIs on iOS). diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index 9c9c693f..25a5542a 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -41,6 +41,7 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String NAME = "RNDocumentPicker"; private static final int READ_REQUEST_CODE = 41; + private static final int PICK_DIR_REQUEST_CODE = 42; private static final String E_ACTIVITY_DOES_NOT_EXIST = "ACTIVITY_DOES_NOT_EXIST"; private static final String E_FAILED_TO_SHOW_PICKER = "FAILED_TO_SHOW_PICKER"; @@ -51,7 +52,7 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String E_UNEXPECTED_EXCEPTION = "UNEXPECTED_EXCEPTION"; private static final String OPTION_TYPE = "type"; - private static final String OPTION_MULIPLE = "multiple"; + private static final String OPTION_MULTIPLE = "multiple"; private static final String OPTION_COPYTO = "copyTo"; private static final String FIELD_URI = "uri"; @@ -61,13 +62,17 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String FIELD_TYPE = "type"; private static final String FIELD_SIZE = "size"; + private final ActivityEventListener activityEventListener = new BaseActivityEventListener() { @Override public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + if (promise == null) { + return; + } if (requestCode == READ_REQUEST_CODE) { - if (promise != null) { - onShowActivityResult(resultCode, data, promise); - } + onShowActivityResult(resultCode, data, promise); + } else if (requestCode == PICK_DIR_REQUEST_CODE) { + onPickDirectoryResult(resultCode, data, promise); } } }; @@ -126,7 +131,7 @@ public void pick(ReadableMap args, Promise promise) { } } - boolean multiple = !args.isNull(OPTION_MULIPLE) && args.getBoolean(OPTION_MULIPLE); + boolean multiple = !args.isNull(OPTION_MULTIPLE) && args.getBoolean(OPTION_MULTIPLE); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple); currentActivity.startActivityForResult(Intent.createChooser(intent, null), READ_REQUEST_CODE, Bundle.EMPTY); @@ -175,6 +180,43 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { } } + @ReactMethod + public void pickDirectory(Promise promise) { + Activity currentActivity = getCurrentActivity(); + + if (currentActivity == null) { + promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist"); + return; + } + this.promise = promise; + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + currentActivity.startActivityForResult(intent, PICK_DIR_REQUEST_CODE, null); + } catch (Exception e) { + sendError(E_FAILED_TO_SHOW_PICKER, "Failed to create directory picker", e); + } + } + + private void onPickDirectoryResult(int resultCode, Intent data, Promise promise) { + if (resultCode == Activity.RESULT_CANCELED) { + sendError(E_DOCUMENT_PICKER_CANCELED, "User canceled directory picker"); + return; + } else if (resultCode != Activity.RESULT_OK) { + sendError(E_UNKNOWN_ACTIVITY_RESULT, "Unknown activity result: " + resultCode); + return; + } + + if (data == null || data.getData() == null) { + sendError(E_INVALID_DATA_RETURNED, "Invalid data returned by intent"); + return; + } + Uri uri = data.getData(); + + WritableMap map = Arguments.createMap(); + map.putString(FIELD_URI, uri.toString()); + promise.resolve(map); + } + private static class ProcessDataTask extends GuardedResultAsyncTask { private final WeakReference weakContext; private final List uris; diff --git a/index.d.ts b/index.d.ts index c9936f61..400e30d9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -78,6 +78,10 @@ declare module 'react-native-document-picker' { name: string; size: number; } + interface DirectoryPickerResponse { + uri: string; + } + type Platform = 'ios' | 'android' | 'windows'; export default class DocumentPicker { static types: PlatformTypes['ios'] | PlatformTypes['android'] | PlatformTypes['windows']; @@ -89,5 +93,6 @@ declare module 'react-native-document-picker' { ): Promise; static isCancel(err?: IError): boolean; static releaseSecureAccess(uris: Array): void; + static pickDirectory(): Promise; } } diff --git a/index.js b/index.js index 4e6dab3e..2328684e 100644 --- a/index.js +++ b/index.js @@ -189,4 +189,12 @@ export default class DocumentPicker { static releaseSecureAccess(uris) { releaseSecureAccess(uris); } + + static pickDirectory() { + if (Platform.OS === 'android') { + return RNDocumentPicker.pickDirectory(); + } else { + return Promise.resolve(null); + } + } }