Skip to content

Commit

Permalink
feat(lib): Moved deletePhotos to use new PHAsset API and added an imp…
Browse files Browse the repository at this point in the history
…lementation for Android (#69)

* deletePhotos works in iOS

* Deletion works on Android.

* Removing unnecessary commented out code.

* Updated typescript typings.

* Made readme more accurate based on being able to retrieve failure from the iOS API.

* Let formatter run, also now rejecting the promise when there's any error on deletion on Android.
  • Loading branch information
Kevin Brown authored and bartolkaruza committed Nov 7, 2019
1 parent 3841038 commit 7850dd5
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 11 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ On Android permission is required to read the external storage. Add below line t
* [`saveToCameraRoll`](#savetocameraroll)
* [`save`](#save)
* [`getPhotos`](#getphotos)
* [`deletePhotos`](#deletephotos)

---

Expand Down Expand Up @@ -208,3 +209,23 @@ render() {
);
}
```
---
### `deletePhotos()`

```javascript
CameraRoll.deletePhotos([uri]);
```

Requests deletion of photos in the camera roll.

On Android, the uri must be a local image or video URI, such as `"file:///sdcard/img.png"`.

On iOS, the uri can be any image URI (including local, remote asset-library and base64 data URIs) or a local video file URI. The user is presented with a dialog box that shows them the asset(s) and asks them to confirm deletion. This is not able to be bypassed as per Apple Developer guidelines.

Returns a Promise which will resolve when the deletion request is completed, or reject if there is a problem during the deletion. On iOS the user is able to cancel the deletion request, which causes a rejection, while on Android the rejection will be due to a system error.

**Parameters:**

| Name | Type | Required | Description |
| ---- | ---------------------- | -------- | ---------------------------------------------------------- |
| uri | string | Yes | See above. |
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.reactnativecommunity.cameraroll;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
Expand Down Expand Up @@ -61,6 +62,7 @@ public class CameraRollModule extends ReactContextBaseJavaModule {
private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD";
private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION";
private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE";
private static final String ERROR_UNABLE_TO_DELETE = "E_UNABLE_TO_DELETE";
private static final String ERROR_UNABLE_TO_FILTER = "E_UNABLE_TO_FILTER";

private static final String ASSET_TYPE_PHOTOS = "Photos";
Expand Down Expand Up @@ -504,4 +506,79 @@ private static void putLocationInfo(
node.putMap("location", location);
}
}

/**
* Delete a set of images.
*
* @param uris array of file:// URIs of the images to delete
* @param promise to be resolved
*/
@ReactMethod
public void deletePhotos(ReadableArray uris, Promise promise) {
if (uris.size() == 0) {
promise.reject(ERROR_UNABLE_TO_DELETE, "Need at least one URI to delete");
} else {
new DeletePhotos(getReactApplicationContext(), uris, promise)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

private static class DeletePhotos extends GuardedAsyncTask<Void, Void> {

private final Context mContext;
private final ReadableArray mUris;
private final Promise mPromise;

public DeletePhotos(ReactContext context, ReadableArray uris, Promise promise) {
super(context);
mContext = context;
mUris = uris;
mPromise = promise;
}

@Override
protected void doInBackgroundGuarded(Void... params) {
ContentResolver resolver = mContext.getContentResolver();

// Set up the projection (we only need the ID)
String[] projection = { MediaStore.Images.Media._ID };

// Match on the file path
String innerWhere = "?";
for (int i = 1; i < mUris.size(); i++) {
innerWhere += ", ?";
}

String selection = MediaStore.Images.Media.DATA + " IN (" + innerWhere + ")";
// Query for the ID of the media matching the file path
Uri queryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

String[] selectionArgs = new String[mUris.size()];
for (int i = 0; i < mUris.size(); i++) {
Uri uri = Uri.parse(mUris.getString(i));
selectionArgs[i] = uri.getPath();
}

Cursor cursor = resolver.query(queryUri, projection, selection, selectionArgs, null);
int deletedCount = 0;

while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
Uri deleteUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);

if (resolver.delete(deleteUri, null, null) == 1) {
deletedCount++;
}
}

cursor.close();

if (deletedCount == mUris.size()) {
mPromise.resolve(null);
} else {
mPromise.reject(ERROR_UNABLE_TO_DELETE,
"Could not delete all media, only deleted " + deletedCount + " photos.");
}
}
}
}
15 changes: 10 additions & 5 deletions ios/RNCCameraRollManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,17 @@ static void RCTResolvePromise(RCTPromiseResolveBlock resolve,
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
NSArray<NSURL *> *assets_ = [RCTConvert NSURLArray:assets];
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHFetchResult<PHAsset *> *fetched =
[PHAsset fetchAssetsWithALAssetURLs:assets_ options:nil];
[PHAssetChangeRequest deleteAssets:fetched];
NSMutableArray *convertedAssets = [NSMutableArray array];

for (NSString *asset in assets) {
[convertedAssets addObject: [asset stringByReplacingOccurrencesOfString:@"ph://" withString:@""]];
}

[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHFetchResult<PHAsset *> *fetched =
[PHAsset fetchAssetsWithLocalIdentifiers:convertedAssets options:nil];
[PHAssetChangeRequest deleteAssets:fetched];
}
completionHandler:^(BOOL success, NSError *error) {
if (success == YES) {
resolve(@(success));
Expand Down
9 changes: 7 additions & 2 deletions js/CameraRoll.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,13 @@ class CameraRoll {
return this.saveToCameraRoll(tag, 'photo');
}

static deletePhotos(photos: Array<string>) {
return RNCCameraRoll.deletePhotos(photos);
/**
* On iOS: requests deletion of a set of photos from the camera roll.
* On Android: Deletes a set of photos from the camera roll.
*
*/
static deletePhotos(photoUris: Array<string>) {
return RNCCameraRoll.deletePhotos(photoUris);
}

/**
Expand Down
7 changes: 3 additions & 4 deletions typings/CameraRoll.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,10 @@ declare namespace CameraRoll {
function saveImageWithTag(tag: string): Promise<string>;

/**
* Delete a photo from the camera roll or media library. photos is an array of photo uri's.
* Delete a photo from the camera roll or media library. photoUris is an array of photo uri's.
*/
function deletePhotos(photos: Array<string>): void;
// deletePhotos: (photos: Array<string>) => void;

function deletePhotos(photoUris: Array<string>): void;

/**
* Saves the photo or video to the camera roll or photo library.
*/
Expand Down

0 comments on commit 7850dd5

Please sign in to comment.