From 1f6499be0a6056d46c65efdd94200eab3ee565c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Poizat?= Date: Wed, 31 May 2023 09:33:11 +0200 Subject: [PATCH] feat(iOS): Add subTypes field (#495) Allow to know PHAssetMediaSubtype on iOS. So for example if a node is a LivePhoto or a Panorama. --- FabricExample/js/CameraRollExample.js | 1 + README.md | 1 + .../cameraroll/CameraRollModule.java | 3 ++ example/js/CameraRollExample.js | 1 + ios/RNCCameraRoll.mm | 39 +++++++++++++++++++ src/CameraRoll.ts | 11 ++++++ src/NativeCameraRollModule.ts | 3 ++ 7 files changed, 59 insertions(+) diff --git a/FabricExample/js/CameraRollExample.js b/FabricExample/js/CameraRollExample.js index e332644..5767295 100644 --- a/FabricExample/js/CameraRollExample.js +++ b/FabricExample/js/CameraRollExample.js @@ -113,6 +113,7 @@ export default class CameraRollExample extends React.Component { {asset.node.group_name} {new Date(asset.node.timestamp * 1000).toString()} {new Date(asset.node.modificationTimestamp * 1000).toString()} + Subtypes: {asset.node.subTypes.join(' ')} diff --git a/README.md b/README.md index cd8a419..2b3ad3f 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ Returns a Promise which when resolved will be of the following shape: * `edges` : {Array} An array of node objects * `node`: {object} An object with the following shape: * `type`: {string} + * `subTypes`: {Array} : An array of subtype strings (see `SubTypes` type). Always [] on Android. * `group_name`: {string} * `image`: {object} : An object with the following shape: * `uri`: {string} diff --git a/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java b/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java index 7dff0ca..698fcd3 100644 --- a/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java +++ b/android/src/main/java/com/reactnativecommunity/cameraroll/CameraRollModule.java @@ -40,6 +40,7 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.bridge.Arguments; import com.facebook.react.common.ReactConstants; import com.facebook.react.module.annotations.ReactModule; @@ -615,6 +616,8 @@ public class CameraRollModule extends NativeCameraRollModuleSpec { int dateAddedIndex, int dateModifiedIndex) { node.putString("type", media.getString(mimeTypeIndex)); + WritableArray subTypes = Arguments.createArray(); + node.putArray("subTypes", subTypes); node.putString("group_name", media.getString(groupNameIndex)); long dateTaken = media.getLong(dateTakenIndex); if (dateTaken == 0L) { diff --git a/example/js/CameraRollExample.js b/example/js/CameraRollExample.js index cb42790..9cde821 100644 --- a/example/js/CameraRollExample.js +++ b/example/js/CameraRollExample.js @@ -113,6 +113,7 @@ export default class CameraRollExample extends React.Component { {asset.node.group_name} {new Date(asset.node.timestamp * 1000).toString()} {new Date(asset.node.modificationTimestamp * 1000).toString()} + Subtypes: {asset.node.subTypes.join(' ')} diff --git a/ios/RNCCameraRoll.mm b/ios/RNCCameraRoll.mm index 65aa2c0..5124bac 100644 --- a/ios/RNCCameraRoll.mm +++ b/ios/RNCCameraRoll.mm @@ -422,6 +422,8 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params ? @"audio" : @"unknown"))); + NSArray *const assetMediaSubtypesLabel = [self mediaSubTypeLabelsForAsset:asset]; + if (includeFileExtension) { NSString *name = [asset valueForKey:@"filename"]; NSString *extension = [name pathExtension]; @@ -433,6 +435,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params [assets addObject:@{ @"node": @{ @"type": assetMediaTypeLabel, // TODO: switch to mimeType? + @"subTypes":assetMediaSubtypesLabel, @"group_name": currentCollectionName, @"image": @{ @"uri": uri, @@ -559,6 +562,8 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId __block NSString *filePath = @""; + NSArray *const assetMediaSubtypesLabel = [self mediaSubTypeLabelsForAsset:asset]; + // check if HEIC extension asset if (convertHeic && asset.mediaType == PHAssetMediaTypeImage && [uniformMimeType isEqual: @"public.heic"]) { // convert to JPEG @@ -589,6 +594,7 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId resolve(@{ @"node": @{ @"type": assetMediaTypeLabel, + @"subTypes":assetMediaSubtypesLabel, @"image": @{ @"filepath": fullPath, @"filename": originalFilename, @@ -631,6 +637,7 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId resolve(@{ @"node": @{ @"type": assetMediaTypeLabel, + @"subTypes":assetMediaSubtypesLabel, @"image": @{ @"filepath": filePath, @"filename": originalFilename, @@ -670,6 +677,38 @@ RCT_EXPORT_METHOD(getPhotoByInternalID:(NSString *)internalId }, false); } +- (NSArray *) mediaSubTypeLabelsForAsset:(PHAsset *)asset { + PHAssetMediaSubtype subtype = asset.mediaSubtypes; + NSMutableArray *mediaSubTypeLabels = [NSMutableArray array]; + + if (subtype & PHAssetMediaSubtypePhotoPanorama) { + [mediaSubTypeLabels addObject:@"PhotoPanorama"]; + } + if (subtype & PHAssetMediaSubtypePhotoHDR) { + [mediaSubTypeLabels addObject:@"PhotoHDR"]; + } + if (subtype & PHAssetMediaSubtypePhotoScreenshot) { + [mediaSubTypeLabels addObject:@"PhotoScreenshot"]; + } + if (subtype & PHAssetMediaSubtypePhotoLive) { + [mediaSubTypeLabels addObject:@"PhotoLive"]; + } + if (subtype & PHAssetMediaSubtypePhotoDepthEffect) { + [mediaSubTypeLabels addObject:@"PhotoDepthEffect"]; + } + if (subtype & PHAssetMediaSubtypeVideoStreamed) { + [mediaSubTypeLabels addObject:@"VideoStreamed"]; + } + if (subtype & PHAssetMediaSubtypeVideoHighFrameRate) { + [mediaSubTypeLabels addObject:@"VideoHighFrameRate"]; + } + if (subtype & PHAssetMediaSubtypeVideoTimelapse) { + [mediaSubTypeLabels addObject:@"VideoTimelapse"]; + } + + return mediaSubTypeLabels; +} + static void checkPhotoLibraryConfig() { #if RCT_DEV diff --git a/src/CameraRoll.ts b/src/CameraRoll.ts index b7ffa53..871e84c 100644 --- a/src/CameraRoll.ts +++ b/src/CameraRoll.ts @@ -32,6 +32,16 @@ export type GroupTypes = | 'PhotoStream' | 'SavedPhotos'; +export type SubTypes = + | 'PhotoPanorama' + | 'PhotoHDR' + | 'PhotoScreenshot' + | 'PhotoLive' + | 'PhotoDepthEffect' + | 'VideoStreamed' + | 'VideoHighFrameRate' + | 'VideoTimelapse'; + export type Include = | 'filename' | 'fileSize' @@ -100,6 +110,7 @@ export type GetPhotosParams = { export type PhotoIdentifier = { node: { type: string; + subTypes: SubTypes; group_name: string; image: { filename: string | null; diff --git a/src/NativeCameraRollModule.ts b/src/NativeCameraRollModule.ts index ec436f8..7bcf589 100644 --- a/src/NativeCameraRollModule.ts +++ b/src/NativeCameraRollModule.ts @@ -3,6 +3,8 @@ // and we want to stay compatible with those import {TurboModuleRegistry, TurboModule} from 'react-native'; +import type {SubTypes} from './CameraRoll'; + type Album = { title: string; count: number; @@ -11,6 +13,7 @@ type Album = { type PhotoIdentifier = { node: { type: string; + subTypes: SubTypes; group_name: string; image: { filename: string | null;