Compare commits

...

39 Commits

Author SHA1 Message Date
marcosrdz
3e343152a6 Merge remote-tracking branch 'upstream/master' 2020-08-10 19:28:32 -04:00
Janic Duplessis
b7a89e3d3b 2.3.3 2020-07-13 13:44:38 -04:00
Jafar Jabr
be1a4980e3
remove deprecated gradle option (#1390)
remove The option 'android.enableUnitTestBinaryResources' is deprecated
which removed from gradle 4, The current default is 'false'
to allow the build using gradle 4
2020-07-13 13:42:09 -04:00
Marcos Rodriguez Vélez
da9bd7db9d
Update ImagePickerManager.m 2020-07-11 23:28:19 -04:00
Marcos Rodriguez Vélez
5bcf00fd6a
Use Catalyst if Target 2020-07-11 00:21:04 -04:00
Marcos Rodriguez Vélez
be4bca9225
Remove Code that blocks from compiling with Catalyst. 2020-07-10 20:35:41 -04:00
Janic Duplessis
e99b8bb5be 2.3.2 2020-07-06 13:46:13 -04:00
Andrei Tofan
18ee85b339
Make sure the calls are execute on the main thread (#1265)
* Make sure the calls are execute on the main thread

* Fix formatting

* Wrap method calls

Co-authored-by: Andrei Tofan <andrei@optinify.io>
2020-07-06 13:43:30 -04:00
Akhmad Syaikhul Hadi
38db13aeb6 Fix failure delivering result info #579 2020-05-07 08:13:08 +02:00
Evan Bacon
3b9cf0347b Update README.md 2020-05-06 14:59:26 +02:00
Ulf Andersson
d3a7ed2ca0 test: Make tests work with >=Java9 by upgrading libraries
- Upgrade RoboElectric from 3.3.2 => 4.3.1
- Upgrade PowerMock from 1.6.2 => 1.6.6
- Removed unused fest-assert-core
- Remove explicit mockito, junit and assertj dependencies, correct
versions are pulled in as transitive depedencies by RoboElectric
2020-05-04 07:33:54 +02:00
Johan du Toit
a2626b3760
Add help wanted notification 2020-04-30 13:03:32 +02:00
Janic Duplessis
a40e85bfc9 2.3.1 2020-02-26 10:28:12 -06:00
Marcus Bergholm
54d62aca89
Add write permission check when accessing camera android (#1295) 2020-02-26 10:26:40 -06:00
Johan Du toit
5ec8f8d9fe 2.3.0 2020-02-10 09:29:12 +02:00
Levi
5368e2acc8 fix documentation 2020-02-10 09:27:16 +02:00
Levi
14be039814 clean up 2020-02-10 09:27:16 +02:00
Levi
6005f36dd7 allow android to save photos to private directory 2020-02-10 09:27:16 +02:00
Johan Du toit
8213cff3dd 2.2.1 2020-01-28 08:31:39 +02:00
Jeff Algera
2ad856a1a7 Code review
Copy the file only if we cannot move it.
2020-01-28 08:11:37 +02:00
Jeff Algera
49e6a775cc Copy file instead of move
iOS 13 beta is not allowing the `moveItemAtURL` command to execute which may cause trimmed clips to fail based on permission.

Instead of moving the file, copy it.
2020-01-28 08:11:37 +02:00
Johan Du toit
d1423768c7 2.2.0 2020-01-22 09:24:51 +02:00
Nathan Greene
eab19caa19 fix: Having a space in the filename on android no longer causes the filetype to be null 2020-01-22 09:21:02 +02:00
Johan-dutoit
bcbde0fa1f 📗 2020-01-21 06:27:41 +02:00
Chris
5d1feb500b fix: issue#1252 - if target is running iOS 11 or greater, do not reference deprecated PhotoKit api 2020-01-21 06:22:09 +02:00
aschnapp
4e699d3a59 use system default picker text color 2020-01-10 10:49:09 +02:00
lizraeli
cd69e948d0 remove commented code 2020-01-09 11:39:53 +02:00
lizraeli
ac142cf9cd Add break statements in switch 2020-01-09 11:39:53 +02:00
Lev Izraelit
997c272f78 Split permission requests for Camera & Gallery 2020-01-09 11:39:53 +02:00
Erik Haider Forsén
085d0b7d08 docs: adds tintColor to API Reference 2019-09-23 15:45:38 +01:00
Johan-dutoit
341e95491b docs (readme): improved linking details 2019-09-05 11:42:50 +01:00
Johan-dutoit
5d1ff8566b docs (readme): improved linking details 2019-09-05 11:23:05 +01:00
Johan-dutoit
5281955b2c 📖 2019-08-30 13:57:42 +01:00
Johan-dutoit
90e7004668 1.1.0 2019-08-30 13:52:33 +01:00
Johan-dutoit
134c5b485b chore(cleanup): Apply prettier 2019-08-30 13:38:24 +01:00
Ryan Nickel
0028ebf64d feat(ios): add support for tintColor 2019-08-30 13:34:29 +01:00
Johan Forslund
aacf1b2d5b fix(android): Support devices with multiple gallery apps 2019-08-30 13:33:06 +01:00
Tomas Harkema
e9e0f87a2a fix(permissions): iOS 11+ doesn't need permissions for picking a single image 2019-08-30 13:32:06 +01:00
hossamnasser938
454fff34df feat(mixed): add support for android 2019-08-29 16:29:05 +01:00
14 changed files with 16261 additions and 158 deletions

View File

@ -8,6 +8,11 @@
A React Native module that allows you to use native UI to select a photo/video from the device library or directly from the camera, like so:
🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧
🚧🚧🚧🚧[Help & Input Wanted](https://github.com/react-native-community/react-native-image-picker/issues/1358) 🚧🚧🚧🚧
🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧
| iOS | Android |
| --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| <img title="iOS" src="https://github.com/react-community/react-native-image-picker/blob/master/images/ios-image.png"> | <img title="Android" src="https://github.com/react-community/react-native-image-picker/blob/master/images/android-image.png"> |
@ -31,6 +36,11 @@ p.s. React Native introduced AndroidX support in 0.60, which is a **breaking cha
```
yarn add react-native-image-picker
# RN >= 0.60
npx pod-install
# RN < 0.60
react-native link react-native-image-picker
```

View File

@ -30,6 +30,12 @@ android {
lintOptions {
abortOnError false
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
repositories {
@ -47,13 +53,13 @@ repositories {
dependencies {
api "com.facebook.react:react-native:+" // From node_modules
testImplementation "junit:junit:4.10"
testImplementation "org.assertj:assertj-core:1.7.0"
testImplementation "org.robolectric:robolectric:3.3.2"
testImplementation('org.robolectric:robolectric:4.3.1') {
// https://github.com/robolectric/robolectric/issues/5245
exclude group: 'com.google.auto.service', module: 'auto-service'
}
testImplementation "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}"
testImplementation "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}"
testImplementation "org.powermock:powermock-module-junit4:${POWERMOCK_VERSION}"
testImplementation "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}"
testImplementation "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}"
testImplementation "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
}

View File

@ -1,6 +1,4 @@
MOCKITO_CORE_VERSION=1.+
POWERMOCK_VERSION=1.6.2
FEST_ASSERT_CORE_VERSION=2.0M10
POWERMOCK_VERSION=1.6.6
ReactNativeImagePicker_compileSdkVersion=28
ReactNativeImagePicker_buildToolsVersion=28.0.3
@ -8,4 +6,4 @@ ReactNativeImagePicker_targetSdkVersion=27
ReactNativeImagePicker_minSdkVersion=16
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true

View File

@ -35,6 +35,7 @@ import com.imagepicker.media.ImageConfig;
import com.imagepicker.permissions.PermissionUtils;
import com.imagepicker.permissions.OnImagePickerPermissionsCallback;
import com.imagepicker.utils.MediaUtils.ReadExifResult;
import com.imagepicker.utils.ReadableMapUtils;
import com.imagepicker.utils.RealPathUtil;
import com.imagepicker.utils.UI;
@ -75,10 +76,13 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
private final int dialogThemeId;
protected Callback callback;
private Callback permissionRequestCallback;
private ReadableMap options;
protected Uri cameraCaptureURI;
private Boolean noData = false;
private Boolean pickVideo = false;
private Boolean pickBoth = false;
private ImageConfig imageConfig = new ImageConfig(null, null, 0, 0, 100, 0, false);
@Deprecated
@ -108,18 +112,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
if (!permissionsGranted)
{
responseHelper.invokeError(callback, "Permissions weren't granted");
responseHelper.invokeError(permissionRequestCallback, "Permissions weren't granted");
return false;
}
switch (requestCode)
{
case REQUEST_PERMISSIONS_FOR_CAMERA:
launchCamera(options, callback);
launchCamera(options, permissionRequestCallback);
break;
case REQUEST_PERMISSIONS_FOR_LIBRARY:
launchImageLibrary(options, callback);
launchImageLibrary(options, permissionRequestCallback);
break;
}
@ -224,6 +228,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@ReactMethod
public void launchCamera(final ReadableMap options, final Callback callback)
{
permissionRequestCallback = callback;
if (!isCameraAvailable())
{
responseHelper.invokeError(callback, "Camera not available");
@ -318,6 +324,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@ReactMethod
public void launchImageLibrary(final ReadableMap options, final Callback callback)
{
permissionRequestCallback = callback;
final Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
responseHelper.invokeError(callback, "can't find current Activity");
@ -347,6 +355,11 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
requestCode = REQUEST_LAUNCH_IMAGE_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
if (pickBoth)
{
libraryIntent.setType("image/* video/*");
}
}
if (libraryIntent.resolveActivity(reactContext.getPackageManager()) == null)
@ -357,7 +370,13 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
try
{
currentActivity.startActivityForResult(libraryIntent, requestCode);
String chooseWhichLibraryTitle = null;
if (ReadableMapUtils.hasAndNotEmptyString(options, "chooseWhichLibraryTitle"))
{
chooseWhichLibraryTitle = options.getString("chooseWhichLibraryTitle");
}
currentActivity.startActivityForResult(Intent.createChooser(libraryIntent, chooseWhichLibraryTitle), requestCode);
}
catch (ActivityNotFoundException e)
{
@ -557,10 +576,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
final int cameraPermission = ActivityCompat
.checkSelfPermission(activity, Manifest.permission.CAMERA);
final boolean permissionsGrated = writePermission == PackageManager.PERMISSION_GRANTED &&
cameraPermission == PackageManager.PERMISSION_GRANTED;
boolean permissionsGranted = false;
if (!permissionsGrated)
switch (requestCode) {
case REQUEST_PERMISSIONS_FOR_LIBRARY:
permissionsGranted = writePermission == PackageManager.PERMISSION_GRANTED;
break;
case REQUEST_PERMISSIONS_FOR_CAMERA:
permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED;
break;
}
if (!permissionsGranted)
{
final Boolean dontAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA);
@ -608,7 +635,19 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
}
else
{
String[] PERMISSIONS = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA};
String[] PERMISSIONS;
switch (requestCode) {
case REQUEST_PERMISSIONS_FOR_LIBRARY:
PERMISSIONS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
break;
case REQUEST_PERMISSIONS_FOR_CAMERA:
PERMISSIONS = new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};
break;
default:
PERMISSIONS = new String[]{};
break;
}
if (activity instanceof ReactActivity)
{
((ReactActivity) activity).requestPermissions(PERMISSIONS, requestCode, listener);
@ -703,20 +742,26 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
private void putExtraFileInfo(@NonNull final String path,
@NonNull final ResponseHelper responseHelper)
{
// size && filename
try {
// size && filename
File f = new File(path);
responseHelper.putDouble("fileSize", f.length());
responseHelper.putString("fileName", f.getName());
// type
String extension = MimeTypeMap.getFileExtensionFromUrl(path);
String fileName = f.getName();
if (extension != "") {
responseHelper.putString("type", MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension));
} else {
int i = fileName.lastIndexOf('.');
if (i > 0) {
extension = fileName.substring(i+1);
responseHelper.putString("type", MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension));
}
}
} catch (Exception e) {
e.printStackTrace();
}
// type
String extension = MimeTypeMap.getFileExtensionFromUrl(path);
if (extension != null) {
responseHelper.putString("type", MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension));
}
}
private void parseOptions(final ReadableMap options) {
@ -726,6 +771,10 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
}
imageConfig = imageConfig.updateFromOptions(options);
pickVideo = false;
pickBoth = false;
if (options.hasKey("mediaType") && options.getString("mediaType").equals("mixed")) {
pickBoth = true;
}
if (options.hasKey("mediaType") && options.getString("mediaType").equals("video")) {
pickVideo = true;
}

View File

@ -73,6 +73,9 @@ public class ResponseHelper
public void invokeResponse(@NonNull final Callback callback)
{
if (callback == null) {
return;
}
callback.invoke(response);
}
}

View File

@ -45,11 +45,32 @@ public class MediaUtils
.append(".jpg")
.toString();
final File path = ReadableMapUtils.hasAndNotNullReadableMap(options, "storageOptions")
&& ReadableMapUtils.hasAndNotEmptyString(options.getMap("storageOptions"), "path")
? new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), options.getMap("storageOptions").getString("path"))
: (!forceLocal ? Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
: reactContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES));
// defaults to Public Pictures Directory
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (ReadableMapUtils.hasAndNotNullReadableMap(options, "storageOptions"))
{
final ReadableMap storageOptions = options.getMap("storageOptions");
if (storageOptions.hasKey("privateDirectory"))
{
boolean saveToPrivateDirectory = storageOptions.getBoolean("privateDirectory");
if (saveToPrivateDirectory)
{
// if privateDirectory is set then save to app's private files directory
path = reactContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
}
if (ReadableMapUtils.hasAndNotEmptyString(storageOptions, "path"))
{
path = new File(path, storageOptions.getString("path"));
}
}
else if (forceLocal)
{
path = reactContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
File result = new File(path, filename);
@ -101,7 +122,7 @@ public class MediaUtils
if (photo == null)
{
return null;
return imageConfig;
}
ImageConfig result = imageConfig;

View File

@ -45,8 +45,7 @@ import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(RobolectricTestRunner.class)
@SuppressStaticInitializationFor("com.facebook.react.common.build.ReactBuildConfig")
@PrepareForTest({Arguments.class})
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@Config(manifest = Config.NONE)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*"})
public class ImagePickerModuleTest
{
private static final int DEFAULT_THEME = R.style.DefaultExplainingPermissionsTheme;
@ -111,4 +110,4 @@ public class ImagePickerModuleTest
}
});
}
}
}

View File

@ -2,6 +2,11 @@
```
yarn add react-native-image-picker
# RN >= 0.60
cd ios && pod install
# RN < 0.60
react-native link react-native-image-picker
```

View File

@ -44,30 +44,33 @@ The `callback` will be called with a response object, refer to [The Response Obj
| option | iOS | Android | Info |
| ----------------------------- | --- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| title | OK | OK | Specify `null` or empty string to remove the title |
| cancelButtonTitle | OK | OK | Specify `null` or empty string to remove this button |
| takePhotoButtonTitle | OK | OK | Specify `null` or empty string to remove this button |
| chooseFromLibraryButtonTitle | OK | OK | Specify `null` or empty string to remove this button |
| customButtons | OK | OK | An array containing objects with the name and title of buttons |
| cameraType | OK | - | 'front' or 'back' |
| mediaType | OK | OK | 'photo', 'video', or 'mixed' on iOS, 'photo' or 'video' on Android |
| maxWidth | OK | OK | Photos only |
| maxHeight | OK | OK | Photos only |
| quality | OK | OK | 0 to 1, photos only |
| videoQuality | OK | OK | 'low', 'medium', or 'high' on iOS, 'low' or 'high' on Android |
| durationLimit | OK | OK | Max video recording time, in seconds |
| rotation | - | OK | Photos only, 0 to 360 degrees of rotation |
| allowsEditing | OK | - | bool - enables built-in iOS functionality to resize the image after selection |
| noData | OK | OK | If true, disables the base64 `data` field from being generated (greatly improves performance on large photos) |
| storageOptions | OK | OK | If this key is provided, the image will be saved in your app's `Documents` directory on iOS, or your app's `Pictures` directory on Android (rather than a temporary directory) |
| storageOptions.skipBackup | OK | - | If true, the photo will NOT be backed up to iCloud |
| storageOptions.path | OK | OK | If set, will save the image at `Documents/[path]/` rather than the root `Documents` for iOS, and `Pictures/[path]/` on Android. |
| storageOptions.cameraRoll | OK | OK | If true, the cropped photo will be saved to the iOS Camera Roll or Android DCIM folder. |
| storageOptions.waitUntilSaved | OK | - | If true, will delay the response callback until after the photo/video was saved to the Camera Roll. If the photo or video was just taken, then the file name and timestamp fields are only provided in the response object when this AND `cameraRoll` are both true. |
| permissionDenied.title | - | OK | Title of explaining permissions dialog. By default `Permission denied`. |
| permissionDenied.text | - | OK | Message of explaining permissions dialog. By default `To be able to take pictures with your camera and choose images from your library.`. |
| permissionDenied.reTryTitle | - | OK | Title of re-try button. By default `re-try` |
| permissionDenied.okTitle | - | OK | Title of ok button. By default `I'm sure` |
| title | OK | OK | Specify `null` or empty string to remove the title |
| cancelButtonTitle | OK | OK | Specify `null` or empty string to remove this button |
| takePhotoButtonTitle | OK | OK | Specify `null` or empty string to remove this button |
| chooseFromLibraryButtonTitle | OK | OK | Specify `null` or empty string to remove this button |
| chooseWhichLibraryTitle | - | OK | Specify `null` or empty string to use default Android title. Is shown when user has multiple apps that can open library. |
| customButtons | OK | OK | An array containing objects with the name and title of buttons |
| tintColor | OK | - | Text color to use on buttons |
| cameraType | OK | - | 'front' or 'back' |
| mediaType | OK | OK | 'photo', 'video', or 'mixed' |
| maxWidth | OK | OK | Photos only |
| maxHeight | OK | OK | Photos only |
| quality | OK | OK | 0 to 1, photos only |
| videoQuality | OK | OK | 'low', 'medium', or 'high' on iOS, 'low' or 'high' on Android |
| durationLimit | OK | OK | Max video recording time, in seconds |
| rotation | - | OK | Photos only, 0 to 360 degrees of rotation |
| allowsEditing | OK | - | bool - enables built-in iOS functionality to resize the image after selection |
| noData | OK | OK | If true, disables the base64 `data` field from being generated (greatly improves performance on large photos) |
| storageOptions | OK | OK | If this key is provided, the image will be saved in your app's `Documents` directory on iOS (rather than a temporary directory). On Android this key does not affect the image location (Android always defaults to the public `Pictures` directory) |
| storageOptions.skipBackup | OK | - | If true, the photo will NOT be backed up to iCloud |
| storageOptions.path | OK | OK | If set, will save the image at `Documents/[path]/` rather than the root `Documents` for iOS, and `Pictures/[path]/` on Android. |
| storageOptions.cameraRoll | OK | OK | If true, the cropped photo will be saved to the iOS Camera Roll or Android DCIM folder. |
| storageOptions.waitUntilSaved | OK | - | If true, will delay the response callback until after the photo/video was saved to the Camera Roll. If the photo or video was just taken, then the file name and timestamp fields are only provided in the response object when this AND `cameraRoll` are both true. |
| storageOptions.privateDirectory | - | OK | If true, the photo will be saved to the apps private files directory (Android/data/your_package/files/Pictures) |
| permissionDenied.title | - | OK | Title of explaining permissions dialog. By default `Permission denied`. |
| permissionDenied.text | - | OK | Message of explaining permissions dialog. By default `To be able to take pictures with your camera and choose images from your library.`. |
| permissionDenied.reTryTitle | - | OK | Title of re-try button. By default `re-try` |
| permissionDenied.okTitle | - | OK | Title of ok button. By default `I'm sure` |
## The Response Object

View File

@ -1,9 +1,11 @@
#import "ImagePickerManager.h"
#import <React/RCTConvert.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
#import <React/RCTUtils.h>
#if !TARGET_OS_MACCATALYST
#import <AssetsLibrary/AssetsLibrary.h>
#endif
@import MobileCoreServices;
@ -25,22 +27,26 @@ RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(launchCamera:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
{
self.callback = callback;
[self launchImagePicker:RNImagePickerTargetCamera options:options];
dispatch_async(dispatch_get_main_queue(), ^{
[self launchImagePicker:RNImagePickerTargetCamera options:options];
});
}
RCT_EXPORT_METHOD(launchImageLibrary:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
{
self.callback = callback;
[self launchImagePicker:RNImagePickerTargetLibrarySingleImage options:options];
dispatch_async(dispatch_get_main_queue(), ^{
[self launchImagePicker:RNImagePickerTargetLibrarySingleImage options:options];
});
}
RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
{
self.callback = callback; // Save the callback so we can use it from the delegate methods
self.options = options;
dispatch_async(dispatch_get_main_queue(), ^{
NSString *title = [self.options valueForKey:@"title"];
if ([title isEqual:[NSNull null]] || title.length == 0) {
title = nil; // A more visually appealing UIAlertControl is displayed with a nil title rather than title = @""
@ -48,15 +54,15 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
NSString *cancelTitle = [self.options valueForKey:@"cancelButtonTitle"];
NSString *takePhotoButtonTitle = [self.options valueForKey:@"takePhotoButtonTitle"];
NSString *chooseFromLibraryButtonTitle = [self.options valueForKey:@"chooseFromLibraryButtonTitle"];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet];
alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
self.callback(@[@{@"didCancel": @YES}]); // Return callback for 'cancel' action (if is required)
}];
[alertController addAction:cancelAction];
if (![takePhotoButtonTitle isEqual:[NSNull null]] && takePhotoButtonTitle.length > 0) {
UIAlertAction *takePhotoAction = [UIAlertAction actionWithTitle:takePhotoButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[self actionHandler:action];
@ -69,7 +75,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
}];
[alertController addAction:chooseFromLibraryAction];
}
// Add custom buttons to action sheet
if ([self.options objectForKey:@"customButtons"] && [[self.options objectForKey:@"customButtons"] isKindOfClass:[NSArray class]]) {
self.customButtons = [self.options objectForKey:@"customButtons"];
@ -81,16 +87,16 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
[alertController addAction:customAction];
}
}
UIViewController *root = RCTPresentedViewController();
/* On iPad, UIAlertController presents a popover view rather than an action sheet like on iPhone. We must provide the location
of the location to show the popover in this case. For simplicity, we'll just display it on the bottom center of the screen
to mimic an action sheet */
of the location to show the popover in this case. For simplicity, we'll just display it on the bottom center of the screen
to mimic an action sheet */
alertController.popoverPresentationController.sourceView = root.view;
alertController.popoverPresentationController.sourceRect = CGRectMake(root.view.bounds.size.width / 2.0, root.view.bounds.size.height, 1.0, 1.0);
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
alertController.popoverPresentationController.permittedArrowDirections = 0;
for (id subview in alertController.view.subviews) {
if ([subview isMemberOfClass:[UIView class]]) {
@ -98,7 +104,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
}
}
}
[root presentViewController:alertController animated:YES completion:nil];
});
}
@ -115,7 +121,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
return;
}
}
if ([action.title isEqualToString:[self.options valueForKey:@"takePhotoButtonTitle"]]) { // Take photo
[self launchImagePicker:RNImagePickerTargetCamera];
}
@ -133,7 +139,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
- (void)launchImagePicker:(RNImagePickerTarget)target
{
self.picker = [[UIImagePickerController alloc] init];
if (target == RNImagePickerTargetCamera) {
#if TARGET_IPHONE_SIMULATOR
self.callback(@[@{@"error": @"Camera not available on simulator"}]);
@ -151,10 +157,10 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
else { // RNImagePickerTargetLibrarySingleImage
self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
}
if ([[self.options objectForKey:@"mediaType"] isEqualToString:@"video"]
|| [[self.options objectForKey:@"mediaType"] isEqualToString:@"mixed"]) {
if ([[self.options objectForKey:@"videoQuality"] isEqualToString:@"high"]) {
self.picker.videoQuality = UIImagePickerControllerQualityTypeHigh;
}
@ -164,7 +170,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
else {
self.picker.videoQuality = UIImagePickerControllerQualityTypeMedium;
}
id durationLimit = [self.options objectForKey:@"durationLimit"];
if (durationLimit) {
self.picker.videoMaximumDuration = [durationLimit doubleValue];
@ -178,13 +184,13 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
} else {
self.picker.mediaTypes = @[(NSString *)kUTTypeImage];
}
if ([[self.options objectForKey:@"allowsEditing"] boolValue]) {
self.picker.allowsEditing = true;
}
self.picker.modalPresentationStyle = UIModalPresentationCurrentContext;
self.picker.delegate = self;
// Check permissions
void (^showPickerViewController)() = ^void() {
dispatch_async(dispatch_get_main_queue(), ^{
@ -192,53 +198,57 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
[root presentViewController:self.picker animated:YES completion:nil];
});
};
if (target == RNImagePickerTargetCamera) {
[self checkCameraPermissions:^(BOOL granted) {
if (!granted) {
self.callback(@[@{@"error": @"Camera permissions not granted"}]);
return;
}
showPickerViewController();
}];
}
else { // RNImagePickerTargetLibrarySingleImage
[self checkPhotosPermissions:^(BOOL granted) {
if (!granted) {
self.callback(@[@{@"error": @"Photo library permissions not granted"}]);
return;
}
if (@available(iOS 11.0, *)) {
showPickerViewController();
}];
} else {
[self checkPhotosPermissions:^(BOOL granted) {
if (!granted) {
self.callback(@[@{@"error": @"Photo library permissions not granted"}]);
return;
}
showPickerViewController();
}];
}
}
}
- (NSString * _Nullable)originalFilenameForAsset:(PHAsset * _Nullable)asset assetType:(PHAssetResourceType)type {
if (!asset) { return nil; }
PHAssetResource *originalResource;
// Get the underlying resources for the PHAsset (PhotoKit)
NSArray<PHAssetResource *> *pickedAssetResources = [PHAssetResource assetResourcesForAsset:asset];
// Find the original resource (underlying image) for the asset, which has the desired filename
for (PHAssetResource *resource in pickedAssetResources) {
if (resource.type == type) {
originalResource = resource;
}
}
return originalResource.originalFilename;
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
dispatch_block_t dismissCompletionBlock = ^{
NSURL *imageURL = [info valueForKey:UIImagePickerControllerReferenceURL];
NSURL *imageURL = [info valueForKey:UIImagePickerControllerPHAsset];
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
NSString *fileName;
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
NSString *tempFileName = [[NSUUID UUID] UUIDString];
@ -256,18 +266,18 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
fileName = videoURL.lastPathComponent;
}
// We default to path to the temporary directory
NSString *path = [[NSTemporaryDirectory()stringByStandardizingPath] stringByAppendingPathComponent:fileName];
// If storage options are provided, we use the documents directory which is persisted
if ([self.options objectForKey:@"storageOptions"] && [[self.options objectForKey:@"storageOptions"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
path = [documentsDirectory stringByAppendingPathComponent:fileName];
// Creates documents subdirectory, if provided
if ([storageOptions objectForKey:@"path"]) {
NSString *newPath = [documentsDirectory stringByAppendingPathComponent:[storageOptions objectForKey:@"path"]];
@ -283,10 +293,10 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
}
}
}
// Create the response object
self.response = [[NSMutableDictionary alloc] init];
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) { // PHOTOS
UIImage *originalImage;
if ([[self.options objectForKey:@"allowsEditing"] boolValue]) {
@ -295,9 +305,17 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
else {
originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
}
if (imageURL) {
PHAsset *pickedAsset = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil].lastObject;
PHAsset *pickedAsset;
if (@available(iOS 11.0, *)) {
pickedAsset = [info objectForKey: UIImagePickerControllerPHAsset];
} else {
#if !TARGET_OS_MACCATALYST
pickedAsset = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil].lastObject;
#endif
}
NSString *originalFilename = [self originalFilenameForAsset:pickedAsset assetType:PHAssetResourceTypePhoto];
self.response[@"fileName"] = originalFilename ?: [NSNull null];
if (pickedAsset.location) {
@ -308,9 +326,10 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
self.response[@"timestamp"] = [[ImagePickerManager ISO8601DateFormatter] stringFromDate:pickedAsset.creationDate];
}
}
// GIFs break when resized, so we handle them differently
if (imageURL && [[imageURL absoluteString] rangeOfString:@"ext=GIF"].location != NSNotFound) {
#if !TARGET_OS_MACCATALYST
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {
ALAssetRepresentation *rep = [asset defaultRepresentation];
@ -319,38 +338,39 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:repSize error:nil];
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
[data writeToFile:path atomically:YES];
NSMutableDictionary *gifResponse = [[NSMutableDictionary alloc] init];
[gifResponse setObject:@(originalImage.size.width) forKey:@"width"];
[gifResponse setObject:@(originalImage.size.height) forKey:@"height"];
BOOL vertical = (originalImage.size.width < originalImage.size.height) ? YES : NO;
[gifResponse setObject:@(vertical) forKey:@"isVertical"];
if (![[self.options objectForKey:@"noData"] boolValue]) {
NSString *dataString = [data base64EncodedStringWithOptions:0];
[gifResponse setObject:dataString forKey:@"data"];
}
NSURL *fileURL = [NSURL fileURLWithPath:path];
[gifResponse setObject:[fileURL absoluteString] forKey:@"uri"];
NSNumber *fileSizeValue = nil;
NSError *fileSizeError = nil;
[fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:&fileSizeError];
if (fileSizeValue){
[gifResponse setObject:fileSizeValue forKey:@"fileSize"];
}
self.callback(@[gifResponse]);
} failureBlock:^(NSError *error) {
self.callback(@[@{@"error": error.localizedFailureReason}]);
}];
#endif
return;
}
UIImage *editedImage = [self fixOrientation:originalImage]; // Rotate the image for upload to web
// If needed, downscale image
float maxWidth = editedImage.size.width;
float maxHeight = editedImage.size.height;
@ -361,7 +381,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
maxHeight = [[self.options valueForKey:@"maxHeight"] floatValue];
}
editedImage = [self downscaleImageIfNecessary:editedImage maxWidth:maxWidth maxHeight:maxHeight];
NSData *data;
NSString *mimeType;
if ([[self.options objectForKey:@"imageFileType"] isEqualToString:@"png"]) {
@ -374,36 +394,37 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
}
[self.response setObject:mimeType forKey:@"type"];
[data writeToFile:path atomically:YES];
if (![[self.options objectForKey:@"noData"] boolValue]) {
NSString *dataString = [data base64EncodedStringWithOptions:0]; // base64 encoded image string
[self.response setObject:dataString forKey:@"data"];
}
BOOL vertical = (editedImage.size.width < editedImage.size.height) ? YES : NO;
[self.response setObject:@(vertical) forKey:@"isVertical"];
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSString *filePath = [fileURL absoluteString];
[self.response setObject:filePath forKey:@"uri"];
// add ref to the original image
NSString *origURL = [imageURL absoluteString];
if (origURL) {
[self.response setObject:origURL forKey:@"origURL"];
[self.response setObject:origURL forKey:@"origURL"];
}
NSNumber *fileSizeValue = nil;
NSError *fileSizeError = nil;
[fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:&fileSizeError];
if (fileSizeValue){
[self.response setObject:fileSizeValue forKey:@"fileSize"];
}
[self.response setObject:@(editedImage.size.width) forKey:@"width"];
[self.response setObject:@(editedImage.size.height) forKey:@"height"];
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
if (storageOptions && [[storageOptions objectForKey:@"cameraRoll"] boolValue] == YES && self.picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
#if !TARGET_OS_MACCATALYST
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([[storageOptions objectForKey:@"waitUntilSaved"] boolValue]) {
[library writeImageToSavedPhotosAlbum:originalImage.CGImage metadata:[info valueForKey:UIImagePickerControllerMediaMetadata] completionBlock:^(NSURL *assetURL, NSError *error) {
@ -426,13 +447,15 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
} else {
[library writeImageToSavedPhotosAlbum:originalImage.CGImage metadata:[info valueForKey:UIImagePickerControllerMediaMetadata] completionBlock:nil];
}
#endif
}
}
else { // VIDEO
NSURL *videoRefURL = info[UIImagePickerControllerReferenceURL];
NSURL *videoRefURL = info[UIImagePickerControllerPHAsset];
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
NSURL *videoDestinationURL = [NSURL fileURLWithPath:path];
#if !TARGET_OS_MACCATALYST
if (videoRefURL) {
PHAsset *pickedAsset = [PHAsset fetchAssetsWithALAssetURLs:@[videoRefURL] options:nil].lastObject;
NSString *originalFilename = [self originalFilenameForAsset:pickedAsset assetType:PHAssetResourceTypeVideo];
@ -445,32 +468,41 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
self.response[@"timestamp"] = [[ImagePickerManager ISO8601DateFormatter] stringFromDate:pickedAsset.creationDate];
}
}
#endif
if ([videoURL.URLByResolvingSymlinksInPath.path isEqualToString:videoDestinationURL.URLByResolvingSymlinksInPath.path] == NO) {
NSFileManager *fileManager = [NSFileManager defaultManager];
// Delete file if it already exists
if ([fileManager fileExistsAtPath:videoDestinationURL.path]) {
[fileManager removeItemAtURL:videoDestinationURL error:nil];
}
if (videoURL) { // Protect against reported crash
NSError *error = nil;
[fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error];
if (error) {
self.callback(@[@{@"error": error.localizedFailureReason}]);
return;
}
NSError *error = nil;
// If we have write access to the source file, move it. Otherwise use copy.
if ([fileManager isWritableFileAtPath:[videoURL path]]) {
[fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error];
} else {
[fileManager copyItemAtURL:videoURL toURL:videoDestinationURL error:&error];
}
if (error) {
self.callback(@[@{@"error": error.localizedFailureReason}]);
return;
}
}
}
[self.response setObject:videoDestinationURL.absoluteString forKey:@"uri"];
if (videoRefURL.absoluteString) {
[self.response setObject:videoRefURL.absoluteString forKey:@"origURL"];
}
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
if (storageOptions && [[storageOptions objectForKey:@"cameraRoll"] boolValue] == YES && self.picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
#if !TARGET_OS_MACCATALYST
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:videoDestinationURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
@ -488,22 +520,23 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
self.response[@"timestamp"] = [[ImagePickerManager ISO8601DateFormatter] stringFromDate:capturedAsset.creationDate];
}
}
self.callback(@[self.response]);
}
}
}];
#endif
}
}
// If storage options are provided, check the skipBackup flag
if ([self.options objectForKey:@"storageOptions"] && [[self.options objectForKey:@"storageOptions"] isKindOfClass:[NSDictionary class]]) {
NSDictionary *storageOptions = [self.options objectForKey:@"storageOptions"];
if ([[storageOptions objectForKey:@"skipBackup"] boolValue]) {
[self addSkipBackupAttributeToItemAtPath:path]; // Don't back up the file to iCloud
}
if ([[storageOptions objectForKey:@"waitUntilSaved"] boolValue] == NO ||
[[storageOptions objectForKey:@"cameraRoll"] boolValue] == NO ||
self.picker.sourceType != UIImagePickerControllerSourceTypeCamera)
@ -515,7 +548,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
self.callback(@[self.response]);
}
};
dispatch_async(dispatch_get_main_queue(), ^{
[picker dismissViewControllerAnimated:YES completion:dismissCompletionBlock];
});
@ -574,12 +607,12 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
- (UIImage*)downscaleImageIfNecessary:(UIImage*)image maxWidth:(float)maxWidth maxHeight:(float)maxHeight
{
UIImage* newImage = image;
// Nothing to do here
if (image.size.width <= maxWidth && image.size.height <= maxHeight) {
return newImage;
}
CGSize scaledSize = CGSizeMake(image.size.width, image.size.height);
if (maxWidth < scaledSize.width) {
scaledSize = CGSizeMake(maxWidth, (maxWidth / scaledSize.width) * scaledSize.height);
@ -587,11 +620,11 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
if (maxHeight < scaledSize.height) {
scaledSize = CGSizeMake((maxHeight / scaledSize.height) * scaledSize.width, maxHeight);
}
// If the pixels are floats, it causes a white line in iOS8 and probably other versions too
scaledSize.width = (int)scaledSize.width;
scaledSize.height = (int)scaledSize.height;
UIGraphicsBeginImageContext(scaledSize); // this will resize
[image drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
newImage = UIGraphicsGetImageFromCurrentImageContext();
@ -599,7 +632,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
NSLog(@"could not scale image");
}
UIGraphicsEndImageContext();
return newImage;
}
@ -607,7 +640,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
if (srcImg.imageOrientation == UIImageOrientationUp) {
return srcImg;
}
CGAffineTransform transform = CGAffineTransformIdentity;
switch (srcImg.imageOrientation) {
case UIImageOrientationDown:
@ -615,13 +648,13 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
transform = CGAffineTransformTranslate(transform, srcImg.size.width, srcImg.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, srcImg.size.width, 0);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, srcImg.size.height);
@ -631,14 +664,14 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
case UIImageOrientationUpMirrored:
break;
}
switch (srcImg.imageOrientation) {
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, srcImg.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, srcImg.size.height, 0);
@ -650,7 +683,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
case UIImageOrientationRight:
break;
}
CGContextRef ctx = CGBitmapContextCreate(NULL, srcImg.size.width, srcImg.size.height, CGImageGetBitsPerComponent(srcImg.CGImage), 0, CGImageGetColorSpace(srcImg.CGImage), CGImageGetBitmapInfo(srcImg.CGImage));
CGContextConcatCTM(ctx, transform);
switch (srcImg.imageOrientation) {
@ -660,12 +693,12 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
case UIImageOrientationRightMirrored:
CGContextDrawImage(ctx, CGRectMake(0,0,srcImg.size.height,srcImg.size.width), srcImg.CGImage);
break;
default:
CGContextDrawImage(ctx, CGRectMake(0,0,srcImg.size.width,srcImg.size.height), srcImg.CGImage);
break;
}
CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
UIImage *img = [UIImage imageWithCGImage:cgimg];
CGContextRelease(ctx);
@ -680,7 +713,7 @@ RCT_EXPORT_METHOD(showImagePicker:(NSDictionary *)options callback:(RCTResponseS
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}

15954
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "react-native-image-picker",
"version": "1.0.2",
"version": "2.3.3",
"description": "A React Native module that allows you to use native UI to select media from the device library or directly from the camera",
"react-native": "src/index.ts",
"types": "lib/typescript/index.d.ts",

View File

@ -9,6 +9,7 @@
import NativeInterface from './internal/nativeInterface';
import {ImagePickerOptions, ImagePickerResponse} from './internal/types';
import {processColor} from 'react-native';
const DEFAULT_OPTIONS: ImagePickerOptions = {
title: 'Select a Photo',
@ -24,6 +25,7 @@ const DEFAULT_OPTIONS: ImagePickerOptions = {
reTryTitle: 're-try',
okTitle: "I'm sure",
},
tintColor: '',
};
type Callback = (response: ImagePickerResponse) => void;
@ -39,7 +41,10 @@ class ImagePicker {
): void {
if (typeof optionsOrCallback === 'function') {
return NativeInterface.showImagePicker(
DEFAULT_OPTIONS,
{
...DEFAULT_OPTIONS,
tintColor: processColor(DEFAULT_OPTIONS.tintColor),
},
optionsOrCallback,
);
}
@ -49,21 +54,35 @@ class ImagePicker {
}
return NativeInterface.showImagePicker(
{...DEFAULT_OPTIONS, ...optionsOrCallback},
{
...DEFAULT_OPTIONS,
...optionsOrCallback,
tintColor: processColor(
optionsOrCallback.tintColor || DEFAULT_OPTIONS.tintColor,
),
},
callback,
);
}
launchCamera(options: ImagePickerOptions, callback: Callback): void {
return NativeInterface.launchCamera(
{...DEFAULT_OPTIONS, ...options},
{
...DEFAULT_OPTIONS,
...options,
tintColor: processColor(options.tintColor || DEFAULT_OPTIONS.tintColor),
},
callback,
);
}
launchImageLibrary(options: ImagePickerOptions, callback: Callback): void {
return NativeInterface.launchImageLibrary(
{...DEFAULT_OPTIONS, ...options},
{
...DEFAULT_OPTIONS,
...options,
tintColor: processColor(options.tintColor || DEFAULT_OPTIONS.tintColor),
},
callback,
);
}

View File

@ -36,6 +36,7 @@ export interface ImagePickerOptions {
cancelButtonTitle?: string;
takePhotoButtonTitle?: string;
chooseFromLibraryButtonTitle?: string;
chooseWhichLibraryTitle?: string;
customButtons?: ImagePickerCustomButtonOptions[];
cameraType?: 'front' | 'back';
mediaType?: 'photo' | 'video' | 'mixed';
@ -49,6 +50,7 @@ export interface ImagePickerOptions {
noData?: boolean;
storageOptions?: ImagePickerStorageOptions;
permissionDenied?: ImagePickerPermissionDeniedOptions;
tintColor?: number | string;
}
export interface ImagePickerStorageOptions {
@ -56,6 +58,7 @@ export interface ImagePickerStorageOptions {
path?: string;
cameraRoll?: boolean;
waitUntilSaved?: boolean;
privateDirectory?: boolean;
}
export interface ImagePickerPermissionDeniedOptions {