From fc1855e0010d6e99b65d078752966d7fcefba9f3 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Sat, 8 Feb 2020 10:17:56 +0700 Subject: [PATCH] feat: get album list (#139) --- README.md | 22 +++++++ .../cameraroll/CameraRollModule.java | 62 +++++++++++++++++++ ios/RNCCameraRollManager.m | 22 +++++++ js/CameraRoll.js | 12 ++++ typings/CameraRoll.d.ts | 11 ++++ 5 files changed, 129 insertions(+) diff --git a/README.md b/README.md index 3a3705336..44b17e0ac 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ On Android permission is required to read the external storage. Add below line t * [`saveToCameraRoll`](#savetocameraroll) * [`save`](#save) +* [`getAlbums`](#getalbums) * [`getPhotos`](#getphotos) * [`deletePhotos`](#deletephotos) @@ -115,6 +116,27 @@ Returns a Promise which will resolve with the new URI. | tag | string | Yes | See above. | | type | enum('photo', 'video') | No | Overrides automatic detection based on the file extension. | +--- +### `getAlbums()` + +```javascript +CameraRoll.getAlbums(params); +``` +Returns a Promise with a list of albums + +**Parameters:** + +* `assetType` : {string} : Specifies filter on asset type. Valid values are: + * `All` // default + * `Videos` + * `Photos` + +**Returns:** + +Array of `Album` object + * title: {string} + * count: {number} + --- ### `getPhotos()` diff --git a/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java b/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java index e3be3a11a..63896c886 100644 --- a/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java +++ b/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java @@ -370,6 +370,68 @@ public class CameraRollModule extends ReactContextBaseJavaModule { } } + @ReactMethod + public void getAlbums(final ReadableMap params, final Promise promise) { + String assetType = params.hasKey("assetType") ? params.getString("assetType") : ASSET_TYPE_ALL; + StringBuilder selection = new StringBuilder("1"); + List selectionArgs = new ArrayList<>(); + String bucketDisplayName = MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME; + if (assetType.equals(ASSET_TYPE_PHOTOS)) { + selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE); + } else if (assetType.equals(ASSET_TYPE_VIDEOS)) { + selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO); + bucketDisplayName = MediaStore.Video.VideoColumns.BUCKET_DISPLAY_NAME; + } else if (assetType.equals(ASSET_TYPE_ALL)) { + selection.append(" AND " + MediaStore.Files.FileColumns.MEDIA_TYPE + " IN (" + + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO + "," + + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + ")"); + } else { + promise.reject( + ERROR_UNABLE_TO_FILTER, + "Invalid filter option: '" + assetType + "'. Expected one of '" + + ASSET_TYPE_PHOTOS + "', '" + ASSET_TYPE_VIDEOS + "' or '" + ASSET_TYPE_ALL + "'." + ); + return; + } + selection.append(") GROUP BY (").append(bucketDisplayName); + String[] projection = new String[]{ + "COUNT(*) as count", + bucketDisplayName, + MediaStore.Images.ImageColumns.DATA + }; + try { + Cursor media = getReactApplicationContext().getContentResolver().query( + MediaStore.Files.getContentUri("external").buildUpon().build(), + projection, + selection.toString(), + selectionArgs.toArray(new String[selectionArgs.size()]), + null); + if (media == null) { + promise.reject(ERROR_UNABLE_TO_LOAD, "Could not get media"); + } else { + WritableArray response = new WritableNativeArray(); + try { + if (media.moveToFirst()) { + do { + String albumName = media.getString(media.getColumnIndex(bucketDisplayName)); + int count = media.getInt(media.getColumnIndex("count")); + WritableMap album = new WritableNativeMap(); + album.putString("title", albumName); + album.putInt("count", count); + response.pushMap(album); + } while (media.moveToNext()); + } + } finally { + media.close(); + promise.resolve(response); + } + } + } catch (Exception e) {} + + } + private static void putPageInfo(Cursor media, WritableMap response, int limit, int offset) { WritableMap pageInfo = new WritableNativeMap(); pageInfo.putBoolean("has_next_page", limit < media.getCount()); diff --git a/ios/RNCCameraRollManager.m b/ios/RNCCameraRollManager.m index c475e9767..7007895fa 100644 --- a/ios/RNCCameraRollManager.m +++ b/ios/RNCCameraRollManager.m @@ -196,6 +196,28 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request requestPhotoLibraryAccess(reject, loadBlock); } +RCT_EXPORT_METHOD(getAlbums:(NSDictionary *)params + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + NSString *const mediaType = [params objectForKey:@"assetType"] ? [RCTConvert NSString:params[@"assetType"]] : @"All"; + PHFetchOptions* options = [[PHFetchOptions alloc] init]; + PHFetchResult *const assetCollectionFetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:options]; + NSMutableArray * result = [NSMutableArray new]; + [assetCollectionFetchResult enumerateObjectsUsingBlock:^(PHAssetCollection * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + PHFetchOptions *const assetFetchOptions = [RCTConvert PHFetchOptionsFromMediaType:mediaType fromTime:0 toTime:0]; + // Enumerate assets within the collection + PHFetchResult *const assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:obj options:assetFetchOptions]; + if (assetsFetchResult.count > 0) { + [result addObject:@{ + @"title": [obj localizedTitle], + @"count": @(assetsFetchResult.count) + }]; + } + }]; + resolve(result); +} + static void RCTResolvePromise(RCTPromiseResolveBlock resolve, NSArray *> *assets, BOOL hasNextPage) diff --git a/js/CameraRoll.js b/js/CameraRoll.js index 60ceb5d65..6e5cb6e91 100644 --- a/js/CameraRoll.js +++ b/js/CameraRoll.js @@ -104,6 +104,15 @@ export type SaveToCameraRollOptions = { type?: 'photo' | 'video' | 'auto', album?: string, }; + +export type GetAlbumsParams = { + assetType?: $Keys, +} + +export type Album = { + title: string, + count: number, +} /** * `CameraRoll` provides access to the local camera roll or photo library. * @@ -168,6 +177,9 @@ class CameraRoll { ): Promise { return CameraRoll.save(tag, {type}); } + static getAlbums(params?: GetAlbumsParams = { assetType: ASSET_TYPE_OPTIONS.All }): Promise { + return RNCCameraRoll.getAlbums(params); + } /** * Returns a Promise with photo identifier objects from the local camera * roll of the device matching shape defined by `getPhotosReturnChecker`. diff --git a/typings/CameraRoll.d.ts b/typings/CameraRoll.d.ts index cdab97c72..930b2f57a 100644 --- a/typings/CameraRoll.d.ts +++ b/typings/CameraRoll.d.ts @@ -62,6 +62,15 @@ declare namespace CameraRoll { }; } + interface GetAlbumsParams { + assetType?: AssetType; + } + + interface Album { + title: string; + count: number; + } + type SaveToCameraRollOptions = { type?: 'photo' | 'video' | 'auto', album?: string, @@ -92,6 +101,8 @@ declare namespace CameraRoll { * roll of the device matching shape defined by `getPhotosReturnChecker`. */ function getPhotos(params: GetPhotosParams): Promise; + + function getAlbums(params: GetAlbumsParams): Promise; } export = CameraRoll;