From 0ecc6cbce7e62f5b98c2459a20e49d1785482f67 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 21 May 2021 22:00:01 +0100 Subject: [PATCH 01/17] Fix typo --- .../elyx0/reactnativedocumentpicker/DocumentPickerModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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..71a84357 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -51,7 +51,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"; @@ -126,7 +126,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); From d86577013be07213f7fd6e1f9b5db2ee69678e56 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 21 May 2021 22:41:41 +0100 Subject: [PATCH 02/17] Update Android module --- .../DocumentPickerModule.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) 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 71a84357..32ece46e 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -9,8 +9,10 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.provider.DocumentsContract; import android.provider.OpenableColumns; +import android.util.Log; import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments; @@ -41,6 +43,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"; @@ -60,6 +63,8 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String FIELD_NAME = "name"; private static final String FIELD_TYPE = "type"; private static final String FIELD_SIZE = "size"; + private static final String FIELD_PATH = "path"; + private final ActivityEventListener activityEventListener = new BaseActivityEventListener() { @Override @@ -68,6 +73,10 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, if (promise != null) { onShowActivityResult(resultCode, data, promise); } + } else if (requestCode == PICK_DIR_REQUEST_CODE) { + if (promise != null) { + onPickDirectoryResult(resultCode, data, promise); + } } } }; @@ -175,6 +184,75 @@ 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()); + + String path = uriToPath(uri); + if (path != null) { + map.putString(FIELD_PATH, path); + } + promise.resolve(map); + } + + private String uriToPath(Uri uri) { + if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + File file = new File(uri.getPath()); + return file.getName(); + } else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + String name = null; + + // URI examples + // internal: content://com.android.externalstorage.documents/tree/primary%3Atest + // sd card: content://com.android.externalstorage.documents/tree/1DEA-0313%3Atest + List pathSegments = uri.getPathSegments(); + if (pathSegments.get(0).equalsIgnoreCase("tree")) { + String[] parts = pathSegments.get(1).split(":", 2); + if (parts[0].equalsIgnoreCase("primary")) { + name = new File(Environment.getExternalStorageDirectory(), parts[1]).getAbsolutePath(); + } else { + name = "/storage/" + parts[0] + "/" + parts[1]; + } + } + Log.d(NAME, "Resolved content URI " + uri + " to path " + name); + return name; + } else { + Log.w(NAME, "Unknown URI scheme: " + uri.getScheme()); + return null; + } + } + private static class ProcessDataTask extends GuardedResultAsyncTask { private final WeakReference weakContext; private final List uris; From 505219af86eb1eb6e582175e544fce7221a8bf9d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 21 May 2021 22:43:26 +0100 Subject: [PATCH 03/17] Update js side --- index.d.ts | 6 +++++- index.js | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index c9936f61..6a85dcaf 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, + path?: string + } type Platform = 'ios' | 'android' | 'windows'; export default class DocumentPicker { static types: PlatformTypes['ios'] | PlatformTypes['android'] | PlatformTypes['windows']; @@ -88,6 +92,6 @@ declare module 'react-native-document-picker' { options: DocumentPickerOptions ): Promise; static isCancel(err?: IError): boolean; - static releaseSecureAccess(uris: Array): void; + static pickDirectory(): Promise } } diff --git a/index.js b/index.js index 4e6dab3e..2e4c3921 100644 --- a/index.js +++ b/index.js @@ -186,7 +186,11 @@ export default class DocumentPicker { return err && err.code === E_DOCUMENT_PICKER_CANCELED; } - static releaseSecureAccess(uris) { - releaseSecureAccess(uris); + static pickDirectory() { + if (Platform.OS === 'android') { + return RNDocumentPicker.pickDirectory(); + } else { + return null; + } } } From 85918c9c1e3e629f3db90eac1872d045c9ce1726 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 22 May 2021 12:52:16 +0100 Subject: [PATCH 04/17] Fix linter errors --- index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 6a85dcaf..009b5538 100644 --- a/index.d.ts +++ b/index.d.ts @@ -79,8 +79,8 @@ declare module 'react-native-document-picker' { size: number; } interface DirectoryPickerResponse { - uri: string, - path?: string + uri: string; + path?: string; } type Platform = 'ios' | 'android' | 'windows'; export default class DocumentPicker { @@ -92,6 +92,6 @@ declare module 'react-native-document-picker' { options: DocumentPickerOptions ): Promise; static isCancel(err?: IError): boolean; - static pickDirectory(): Promise + static pickDirectory(): Promise; } } From 004f9715af3f3c9678933d22969469d953dd18a3 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 22 May 2021 13:40:31 +0100 Subject: [PATCH 05/17] Update README --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 05e99b8d..3237eb47 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,22 @@ 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. + +##### `path`: + +Actual path to the selected directory. May not be set if the dir picker could not translate the URI to a path. + + ### `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). From d2747e6f2ed7593f44c64485e6b99d1b4da96ab1 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 12:48:44 +0100 Subject: [PATCH 06/17] Always set the path field, even if null --- .../reactnativedocumentpicker/DocumentPickerModule.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 32ece46e..4b96826f 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -218,11 +218,7 @@ private void onPickDirectoryResult(int resultCode, Intent data, Promise promise) WritableMap map = Arguments.createMap(); map.putString(FIELD_URI, uri.toString()); - - String path = uriToPath(uri); - if (path != null) { - map.putString(FIELD_PATH, path); - } + map.putString(FIELD_PATH, uriToPath(uri)); promise.resolve(map); } From c5e8c9ee798a8902205b17f3e8a2c03d046246ad Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 21:10:08 +0100 Subject: [PATCH 07/17] Fix DirectoryPickerResponse.path --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 009b5538..6e4bbccd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -80,7 +80,7 @@ declare module 'react-native-document-picker' { } interface DirectoryPickerResponse { uri: string; - path?: string; + path: string | null; } type Platform = 'ios' | 'android' | 'windows'; export default class DocumentPicker { From 5eb8fb6694e6fb6db59b7ff3cfa4f7b7c1b03566 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 21:15:07 +0100 Subject: [PATCH 08/17] Address code review comments --- .../reactnativedocumentpicker/DocumentPickerModule.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 4b96826f..4503353f 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -69,14 +69,13 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { 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); - } } else if (requestCode == PICK_DIR_REQUEST_CODE) { - if (promise != null) { onPickDirectoryResult(resultCode, data, promise); - } } } }; From 28acdab6fd29a2c3f539334882775619c930e01f Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 21:20:52 +0100 Subject: [PATCH 09/17] Restore accidentally removed methods --- index.d.ts | 1 + index.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/index.d.ts b/index.d.ts index 6e4bbccd..b09f8f7e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -92,6 +92,7 @@ declare module 'react-native-document-picker' { options: DocumentPickerOptions ): Promise; static isCancel(err?: IError): boolean; + static releaseSecureAccess(uris: Array): void; static pickDirectory(): Promise; } } diff --git a/index.js b/index.js index 2e4c3921..13cb5e51 100644 --- a/index.js +++ b/index.js @@ -186,6 +186,10 @@ export default class DocumentPicker { return err && err.code === E_DOCUMENT_PICKER_CANCELED; } + static releaseSecureAccess(uris) { + releaseSecureAccess(uris); + } + static pickDirectory() { if (Platform.OS === 'android') { return RNDocumentPicker.pickDirectory(); From 895548f682a6975810a086de84beaf06de043eb9 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 21:22:18 +0100 Subject: [PATCH 10/17] Fix formatting --- .../elyx0/reactnativedocumentpicker/DocumentPickerModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4503353f..c2648c95 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -73,9 +73,9 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, return; } if (requestCode == READ_REQUEST_CODE) { - onShowActivityResult(resultCode, data, promise); + onShowActivityResult(resultCode, data, promise); } else if (requestCode == PICK_DIR_REQUEST_CODE) { - onPickDirectoryResult(resultCode, data, promise); + onPickDirectoryResult(resultCode, data, promise); } } }; From e006644f451ad5870714564589b88b9713beb96a Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 21:25:02 +0100 Subject: [PATCH 11/17] Indicate that pickDirectory can return null --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index b09f8f7e..3833b18d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -93,6 +93,6 @@ declare module 'react-native-document-picker' { ): Promise; static isCancel(err?: IError): boolean; static releaseSecureAccess(uris: Array): void; - static pickDirectory(): Promise; + static pickDirectory(): Promise | null; } } From 2a250619ab85f3169be96ca971d873746866dc7f Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 12 Jun 2021 21:28:28 +0100 Subject: [PATCH 12/17] Make linter happy --- index.d.ts | 2 +- index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 3833b18d..76d20a65 100644 --- a/index.d.ts +++ b/index.d.ts @@ -92,7 +92,7 @@ declare module 'react-native-document-picker' { options: DocumentPickerOptions ): Promise; static isCancel(err?: IError): boolean; - static releaseSecureAccess(uris: Array): void; + static releaseSecureAccess(uris: Array): void; static pickDirectory(): Promise | null; } } diff --git a/index.js b/index.js index 13cb5e51..8844cc4f 100644 --- a/index.js +++ b/index.js @@ -187,7 +187,7 @@ export default class DocumentPicker { } static releaseSecureAccess(uris) { - releaseSecureAccess(uris); + releaseSecureAccess(uris); } static pickDirectory() { From a4b14ab422157bdc23732e0e1c3b8c582dea3f30 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 15 Jun 2021 07:59:30 +0100 Subject: [PATCH 13/17] Only return the URI --- .../DocumentPickerModule.java | 31 ------------------- index.d.ts | 2 +- 2 files changed, 1 insertion(+), 32 deletions(-) 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 c2648c95..25a5542a 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -9,10 +9,8 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.provider.DocumentsContract; import android.provider.OpenableColumns; -import android.util.Log; import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments; @@ -63,7 +61,6 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String FIELD_NAME = "name"; private static final String FIELD_TYPE = "type"; private static final String FIELD_SIZE = "size"; - private static final String FIELD_PATH = "path"; private final ActivityEventListener activityEventListener = new BaseActivityEventListener() { @@ -217,37 +214,9 @@ private void onPickDirectoryResult(int resultCode, Intent data, Promise promise) WritableMap map = Arguments.createMap(); map.putString(FIELD_URI, uri.toString()); - map.putString(FIELD_PATH, uriToPath(uri)); promise.resolve(map); } - private String uriToPath(Uri uri) { - if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { - File file = new File(uri.getPath()); - return file.getName(); - } else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { - String name = null; - - // URI examples - // internal: content://com.android.externalstorage.documents/tree/primary%3Atest - // sd card: content://com.android.externalstorage.documents/tree/1DEA-0313%3Atest - List pathSegments = uri.getPathSegments(); - if (pathSegments.get(0).equalsIgnoreCase("tree")) { - String[] parts = pathSegments.get(1).split(":", 2); - if (parts[0].equalsIgnoreCase("primary")) { - name = new File(Environment.getExternalStorageDirectory(), parts[1]).getAbsolutePath(); - } else { - name = "/storage/" + parts[0] + "/" + parts[1]; - } - } - Log.d(NAME, "Resolved content URI " + uri + " to path " + name); - return name; - } else { - Log.w(NAME, "Unknown URI scheme: " + uri.getScheme()); - return null; - } - } - 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 76d20a65..b0dc863a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -80,8 +80,8 @@ declare module 'react-native-document-picker' { } interface DirectoryPickerResponse { uri: string; - path: string | null; } + type Platform = 'ios' | 'android' | 'windows'; export default class DocumentPicker { static types: PlatformTypes['ios'] | PlatformTypes['android'] | PlatformTypes['windows']; From 43b21fd27c41b9b79e1197aecc027ad2dc3544e7 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 15 Jun 2021 08:01:41 +0100 Subject: [PATCH 14/17] Remove path from the docs --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 3237eb47..340a9544 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,6 @@ Open a system directory picker. Returns a promise that resolves to the directory The URI of the selected directory. Usually it will be a content URI. -##### `path`: - -Actual path to the selected directory. May not be set if the dir picker could not translate the URI to a path. - ### `DocumentPicker.types.*` From d1fdf0229efe05bf8b1a4a9518ee0f1b98c4ce3e Mon Sep 17 00:00:00 2001 From: Roman Musin Date: Tue, 15 Jun 2021 15:37:46 +0000 Subject: [PATCH 15/17] Delete trailing spaces --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index b0dc863a..f4612b13 100644 --- a/index.d.ts +++ b/index.d.ts @@ -81,7 +81,7 @@ declare module 'react-native-document-picker' { interface DirectoryPickerResponse { uri: string; } - + type Platform = 'ios' | 'android' | 'windows'; export default class DocumentPicker { static types: PlatformTypes['ios'] | PlatformTypes['android'] | PlatformTypes['windows']; From 43e0ad86f2b4a5c67ec0dc0941f84e49d4ae9dc8 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 15 Jun 2021 22:58:31 +0200 Subject: [PATCH 16/17] Update index.d.ts --- index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index f4612b13..400e30d9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -93,6 +93,6 @@ declare module 'react-native-document-picker' { ): Promise; static isCancel(err?: IError): boolean; static releaseSecureAccess(uris: Array): void; - static pickDirectory(): Promise | null; + static pickDirectory(): Promise; } } From c2f5ef547d9a31c0058ce819a93d955363ec10ee Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Tue, 15 Jun 2021 22:59:11 +0200 Subject: [PATCH 17/17] Update index.js --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 8844cc4f..2328684e 100644 --- a/index.js +++ b/index.js @@ -194,7 +194,7 @@ export default class DocumentPicker { if (Platform.OS === 'android') { return RNDocumentPicker.pickDirectory(); } else { - return null; + return Promise.resolve(null); } } }