feat(lib): Moved deletePhotos to use new PHAsset API and added an implementation 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.
This commit is contained in:
Kevin Brown 2019-11-08 00:13:24 +11:00 committed by Bartol Karuza
parent 384103860f
commit 7850dd538f
5 changed files with 118 additions and 11 deletions

View File

@ -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)
---
@ -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. |

View File

@ -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;
@ -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";
@ -504,4 +506,79 @@ public class CameraRollModule extends ReactContextBaseJavaModule {
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.");
}
}
}
}

View File

@ -367,12 +367,17 @@ RCT_EXPORT_METHOD(deletePhotos:(NSArray<NSString *>*)assets
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));

View File

@ -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);
}
/**

View File

@ -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.
*/