Skip to content
This repository has been archived by the owner on Apr 1, 2023. It is now read-only.

feat: implement pickDirectory method #71

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const appendFileToFormData = async () => {

<docgen-index>

* [`pickDirectory()`](#pickdirectory)
* [`pickFiles(...)`](#pickfiles)
* [`pickImages(...)`](#pickimages)
* [`pickMedia(...)`](#pickmedia)
Expand All @@ -104,6 +105,25 @@ const appendFileToFormData = async () => {
<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->

### pickDirectory()

```typescript
pickDirectory() => Promise<PickDirectoryResult>
```

Pick a directory.

Returns a security-scoped URL for the directory that permits your app to access content outside its container.

Only available on Android and iOS.

**Returns:** <code>Promise&lt;<a href="#pickdirectoryresult">PickDirectoryResult</a>&gt;</code>

**Since:** 0.5.7

--------------------


### pickFiles(...)

```typescript
Expand Down Expand Up @@ -193,6 +213,13 @@ Only available on Android and iOS.
### Interfaces


#### PickDirectoryResult

| Prop | Type | Description | Since |
| ---------- | ------------------- | -------------------------- | ----- |
| **`path`** | <code>string</code> | The path of the directory. | 0.5.7 |


#### PickFilesResult

| Prop | Type |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.capawesome.capacitorjs.plugins.filepicker;

import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.util.Log;

import androidx.annotation.Nullable;

import com.getcapacitor.JSArray;
import com.getcapacitor.Logger;

import org.json.JSONException;

import java.util.LinkedList;
import java.util.List;

public class FilePickerHelper {

@Nullable
public static String[] parseTypesOption(@Nullable JSArray types) {
if (types == null) {
return null;
}
try {
List<String> typesList = types.toList();
if (typesList.contains("text/csv")) {
typesList.add("text/comma-separated-values");
}
return typesList.toArray(new String[0]);
} catch (JSONException exception) {
Logger.error("parseTypesOption failed.", exception);
return null;
}
}

public static void traverseDirectoryEntries(ContentResolver contentResolver, Uri rootUri) {
Uri childrenUri;
try {
//for childs and sub child dirs
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getDocumentId(rootUri));
} catch (Exception e) {
// for parent dir
childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri));
}

List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);

while(!dirNodes.isEmpty()) {
// Get the item from the top
childrenUri = dirNodes.remove(0);
Cursor c = contentResolver.query(childrenUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_MIME_TYPE}, null, null, null);
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
Log.d("sss", "childrenUri: " + childrenUri + ", docId: " + docId + ", name: " + name + ", mime: " + mime);
//if(isDirectory(mime)) {
// final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
// dirNodes.add(newNode);
//}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.util.Log;
import androidx.activity.result.ActivityResult;
import androidx.annotation.Nullable;
Expand All @@ -14,6 +15,8 @@
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
Expand All @@ -23,6 +26,8 @@ public class FilePickerPlugin extends Plugin {

public static final String TAG = "FilePickerPlugin";

public static final String ERROR_PICK_DIRECTORY_FAILED = "pickDirectory failed.";
public static final String ERROR_PICK_DIRECTORY_CANCELED = "pickDirectory canceled.";
public static final String ERROR_PICK_FILE_FAILED = "pickFiles failed.";
public static final String ERROR_PICK_FILE_CANCELED = "pickFiles canceled.";
private FilePicker implementation;
Expand All @@ -31,12 +36,30 @@ public void load() {
implementation = new FilePicker(this.getBridge());
}

@PluginMethod
public void pickDirectory(PluginCall call) {
try {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);

startActivityForResult(call, intent, "pickDirectoryResult");
} catch (Exception ex) {
String message = ex.getMessage();
Log.e(TAG, message);
call.reject(message);
}
}

@PluginMethod
public void pickFiles(PluginCall call) {
try {
JSArray types = call.getArray("types", null);
boolean multiple = call.getBoolean("multiple", false);
String[] parsedTypes = parseTypesOption(types);
String[] parsedTypes = FilePickerHelper.parseTypesOption(types);

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
Expand Down Expand Up @@ -111,20 +134,28 @@ public void pickVideos(PluginCall call) {
}
}

@Nullable
private String[] parseTypesOption(@Nullable JSArray types) {
if (types == null) {
return null;
}
@ActivityCallback
private void pickDirectoryResult(PluginCall call, ActivityResult result) {
try {
List<String> typesList = types.toList();
if (typesList.contains("text/csv")) {
typesList.add("text/comma-separated-values");
if (call == null) {
return;
}
return typesList.toArray(new String[0]);
} catch (JSONException exception) {
Logger.error("parseTypesOption failed.", exception);
return null;
int resultCode = result.getResultCode();
switch (resultCode) {
case Activity.RESULT_OK:
JSObject callResult = createPickDirectoryResult(result.getData());
call.resolve(callResult);
break;
case Activity.RESULT_CANCELED:
call.reject(ERROR_PICK_DIRECTORY_CANCELED);
break;
default:
call.reject(ERROR_PICK_DIRECTORY_FAILED);
}
} catch (Exception ex) {
String message = ex.getMessage();
Log.e(TAG, message);
call.reject(message);
}
}

Expand Down Expand Up @@ -154,6 +185,14 @@ private void pickFilesResult(PluginCall call, ActivityResult result) {
}
}

private JSObject createPickDirectoryResult(Intent data) {
Uri uri = data.getData();
JSObject result = new JSObject();
FilePickerHelper.traverseDirectoryEntries(getActivity().getContentResolver(), uri);
result.put("path", implementation.getPathFromUri(uri));
return result;
}

private JSObject createPickFilesResult(@Nullable Intent data, boolean readData) {
JSObject callResult = new JSObject();
List<JSObject> filesResultList = new ArrayList<>();
Expand Down
22 changes: 22 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
export interface FilePickerPlugin {
/**
* Pick a directory.
*
* Returns a security-scoped URL for the directory that permits your app to access content outside its container.
*
* Only available on Android and iOS.
*
* @since 0.5.7
*/
pickDirectory(): Promise<PickDirectoryResult>;
/**
* Open the file picker that allows the user to select one or more files.
*/
Expand Down Expand Up @@ -35,6 +45,18 @@ export interface FilePickerPlugin {
pickVideos(options?: PickVideosOptions): Promise<PickVideosResult>;
}

/**
* @since 0.5.7
*/
export interface PickDirectoryResult {
/**
* The path of the directory.
*
* @since 0.5.7
*/
path: string;
}

export interface PickFilesOptions {
/**
* List of accepted file types.
Expand Down
5 changes: 5 additions & 0 deletions src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { WebPlugin } from '@capacitor/core';
import type {
File as FileModel,
FilePickerPlugin,
PickDirectoryResult,
PickFilesOptions,
PickFilesResult,
PickImagesOptions,
Expand All @@ -16,6 +17,10 @@ import type {
export class FilePickerWeb extends WebPlugin implements FilePickerPlugin {
public readonly ERROR_PICK_FILE_CANCELED = 'pickFiles canceled.';

public async pickDirectory(): Promise<PickDirectoryResult> {
throw this.unimplemented('Not implemented on web.');
}

public async pickFiles(options?: PickFilesOptions): Promise<PickFilesResult> {
const pickedFiles = await this.openFilePicker(options);
if (!pickedFiles) {
Expand Down