├── .gitignore ├── .npmignore ├── README.md ├── RNCustomizedImagePicker.podspec ├── android ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── mg │ │ └── app │ │ ├── Compression.java │ │ ├── PickerModule.java │ │ └── PickerPackage.java │ └── res │ ├── mipmap-hdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── images ├── pic.png ├── pic2.png └── pic3.png ├── index.android.js ├── index.js ├── ios ├── ImageCropPicker.h ├── ImageCropPicker.m ├── ImageCropPicker.xcodeproj │ └── project.pbxproj ├── NSDictionary+SYSafeConvert.h └── NSDictionary+SYSafeConvert.m └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | /android/local.properties 32 | # node.js 33 | # 34 | node_modules/ 35 | 36 | # BUCK 37 | buck-out/ 38 | \.buckd/ 39 | *.keystore 40 | !debug.keystore 41 | !xiushang.keystore 42 | # fastlane 43 | # 44 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 45 | # screenshots whenever they are needed. 46 | # For more information about the recommended setup visit: 47 | # https://docs.fastlane.tools/best-practices/source-control/ 48 | 49 | */fastlane/report.xml 50 | */fastlane/Preview.html 51 | */fastlane/screenshots 52 | 53 | # Bundle artifact 54 | #*.jsbundle 55 | 56 | # CocoaPods 57 | /ios/Pods/ 58 | /*.lock 59 | /*.log 60 | /ios/xiushangApp.xcworkspace/contents.xcworkspacedata 61 | /ios/xiushangApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist 62 | /ios/Podfile.lock 63 | 64 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | example 3 | node_modules 4 | build 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-customized-image-picker 2 | 3 | iOS/Android image picker with support for camera, video compression, multiple images and cropping 4 | 5 | ## Result 6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 | ## Usage 14 | 15 | 16 | 17 | 18 |

19 | 20 | npm version 21 | 22 | 23 | npm downloads 24 | 25 | 26 | build:started 27 | 28 | 29 | 30 | 31 |

32 | 33 | #### example 34 | 35 | https://github.com/shijingsh/pickExample 36 | 37 | #### Import library 38 | 39 | ```javascript 40 | import ImagePicker from "react-native-customized-image-picker"; 41 | ``` 42 | 43 | #### Select from gallery 44 | 45 | Call single image picker 46 | 47 | ```javascript 48 | ImagePicker.openPicker({}).then(image => { 49 | console.log(image); 50 | }); 51 | ``` 52 | 53 | Call multiple image picker 54 | 55 | ```javascript 56 | ImagePicker.openPicker({ 57 | multiple: true 58 | }).then(images => { 59 | console.log(images); 60 | }); 61 | ``` 62 | 63 | ### Select from camera 64 | 65 | ```javascript 66 | ImagePicker.openCamera({ 67 | width: 300, 68 | height: 400, 69 | cropping: true 70 | }).then(image => { 71 | console.log(image); 72 | }); 73 | ``` 74 | 75 | Select video 76 | 77 | ```javascript 78 | ImagePicker.openCamera({ 79 | width: 300, 80 | height: 400, 81 | isVideo: true 82 | }).then(image => { 83 | console.log(image); 84 | }); 85 | ``` 86 | 87 | ### Optional cleanup 88 | 89 | Module is creating tmp images which are going to be cleaned up automatically somewhere in the future. If you want to force cleanup, you can use `clean` to clean all tmp files. 90 | Delete the cut, compression, and photographed pictures. 91 | 92 | ```javascript 93 | ImagePicker.clean() 94 | .then(() => { 95 | console.log("removed all tmp images from tmp directory"); 96 | }) 97 | .catch(e => { 98 | console.log(e); 99 | }); 100 | ``` 101 | 102 | #### Request Object 103 | 104 | | Property | Type | Description | 105 | | ---------------------- | :----------------------: | :-------------------------------------------------------------------- | 106 | | cropping | bool (default false) | Enable or disable cropping | 107 | | width | number(default 200) | Width of result image when used with `cropping` option | 108 | | height | number(default 200) | Height of result image when used with `cropping` option | 109 | | multiple | bool (default false) | Enable or disable multiple image selection | 110 | | isCamera | bool (default false) | Enable or disable camera selection | 111 | | openCameraOnStart | bool (default false) | Enable or disable turn on the camera when it starts | 112 | | returnAfterShot | bool (default false) | Enable or disable pictures taken directly | 113 | | multipleShot | bool (default false) | Enable or disable Capture image multiple time | 114 | | maxSize | number (default 9) | set image count | 115 | | spanCount | number (default 3) | Set the number of pictures displayed in a row | 116 | | includeBase64 | bool (default false) | Enable or disable includeBase64 | 117 | | compressQuality | number([0-100]) | Picture compression ratio | 118 | | minCompressSize | number | Setting the minimum size of the compressed file(kb) | 119 | | title | string | Sets the title of the page | 120 | | isVideo | bool (default false) | Enable or disable video only | 121 | | isSelectBoth | bool (default false) | Enable or disable select both images and videos | 122 | | isHidePreview | bool (default false) | Enable or disable hidden preview button | 123 | | isHideVideoPreview | bool (default false) | Enable or disable hidden video preview button | 124 | | isPlayGif | bool (default false) | Enable or disable play gif | 125 | | hideCropBottomControls | bool (default true) | Enable or disable crop controls | 126 | | aspectRatioX | number (default 1) | Set an aspect ratio X for crop bounds. | 127 | | aspectRatioY | number (default 1) | Set an aspect ratio Y for crop bounds. | 128 | | videoQuality | number (default 1) | 1: high 0: low. | 129 | | imageLoader | string (default "GLIDE") | Sets the imageLoader of the page,enum(PICASSO,GLIDE,FRESCO,UNIVERSAL) | 130 | 131 | #### Response Object 132 | 133 | | Property | Type | Description | 134 | | -------- | :----: | :----------------------------------------------- | 135 | | path | string | Selected image location | 136 | | width | number | Selected image width | 137 | | height | number | Selected image height | 138 | | mime | string | Selected image MIME type (image/jpeg, image/png) | 139 | | size | number | Selected image size in bytes | 140 | | data | base64 | Optional base64 selected file representation | 141 | 142 | ## Install 143 | 144 | ``` 145 | npm i react-native-customized-image-picker --save 146 | yarn add react-native-customized-image-picker 147 | ``` 148 | 149 | #### android 150 | 151 | #### add permission /android/app/src/main/AndroidManifest.xml 152 | 153 | ```xml 154 | 155 | 156 | 157 | 158 | 159 | 160 | ``` 161 | 162 | ### iOS 163 | 164 | - [TZImagePickerController](https://github.com/banchichen/TZImagePickerController) 165 | 166 | #### info.plist add the following to the file 167 | 168 | ``` 169 | NSCameraUsageDescription 170 | 1 171 | NSLocationWhenInUseUsageDescription 172 | 173 | NSPhotoLibraryAddUsageDescription 174 | 1 175 | NSPhotoLibraryUsageDescription 176 | 1 177 | ``` 178 | auto linked 179 | 180 | #### pod install 181 | cd ios and run 182 | ```bash 183 | pod install 184 | ``` 185 | 186 | ## Setting themes 187 | 188 | #### Setting language 189 | 190 | - Add file gallery_strings.xml under the directory "yourProject\android\app\src\main\res\values". 191 | 192 | ```xml 193 | 194 | Load more 195 | No more 196 | Loading… 197 | 198 | Complete 199 | Complete(%1$d/%2$d) 200 | You can only choose %1$d photos 201 | %1$d/%2$d 202 | photos 203 | video 204 | App request to read your album 205 | >App request to Camera 206 | Absolutely empty 207 | The device has no camera 208 | Camera not available 209 | preview 210 | All pictures 211 | All video 212 | Photograph 213 | Selected 214 | pictures 215 | cut 216 | record video 217 | 218 | ``` 219 | 220 | #### Setting style 221 | 222 | - modify file styles.xml under the directory "yourProject\android\app\src\main\res\values". 223 | 224 | ```xml 225 | 226 | 229 | 261 | 262 | 266 | 267 | ``` 268 | 269 | - modify file AndroidManifest.xml . 270 | 271 | ```xml 272 | 277 | 278 | 286 | 287 | 291 | 292 | 293 | ``` 294 | 295 | ##### Important content 296 | 297 | - xmlns:tools="http://schemas.android.com/tools" 298 | - tools:replace="android:theme" 299 | - android:theme="@style/AppTheme" 300 | - cn.finalteam.rxgalleryfinal.ui.activity.MediaActivity Theme_Light.Default 301 | 302 | ## License 303 | 304 | _MIT_ 305 | -------------------------------------------------------------------------------- /RNCustomizedImagePicker.podspec: -------------------------------------------------------------------------------- 1 | 2 | require 'json' 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "RNCustomizedImagePicker" 7 | s.version = package["version"] 8 | s.summary = package['description'] 9 | s.homepage = "https://github.com/liukefu2050/react-native-customized-image-picker" 10 | s.license = "MIT" 11 | s.author = { "author" => "liukefu2050@sina.com" } 12 | s.platform = :ios, "10.0" 13 | s.source = { :git => "https://github.com/liukefu2050/react-native-customized-image-picker.git", :tag => "master" } 14 | s.source_files = "**/*.{h,m}" 15 | s.requires_arc = true 16 | s.resource = "TZImagePickerController/TZImagePickerController.bundle" 17 | 18 | s.dependency "React" 19 | s.dependency "TZImagePickerController" 20 | 21 | end 22 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def safeExtGet(prop, fallback) { 4 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 5 | } 6 | def _buildToolsVersion = safeExtGet("buildToolsVersion", "30.0.2") 7 | def _compileSdkVersion = safeExtGet("compileSdkVersion", 30) 8 | def _glideVersion = safeExtGet("glideVersion", "3.7.0") 9 | def _minSdkVersion = safeExtGet("minSdkVersion", 16) 10 | def _reactNativeVersion = safeExtGet("reactNativeVersion", "+") 11 | def _targetSdkVersion = safeExtGet("targetSdkVersion", 30) 12 | 13 | android { 14 | compileSdkVersion _compileSdkVersion 15 | buildToolsVersion _buildToolsVersion 16 | 17 | defaultConfig { 18 | minSdkVersion _minSdkVersion 19 | targetSdkVersion _targetSdkVersion 20 | versionCode 1 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | lintOptions { 27 | abortOnError false 28 | } 29 | } 30 | 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | testImplementation 'junit:junit:4.12' 35 | implementation 'com.android.support:appcompat-v7:28.0.0' 36 | implementation "com.facebook.react:react-native:${_reactNativeVersion}" 37 | 38 | //implementation ('com.github.shijingsh:RxCustomizedImagePicker:1.3.0'){ 39 | // exclude module:'fileprovider' 40 | //} 41 | implementation 'com.github.shijingsh:RxCustomizedImagePicker:1.3.1' 42 | //rxgalleryfinal依赖appcompat-v7和recyclerview-v7扩展卡库 43 | implementation 'com.android.support:recyclerview-v7:28.0.0' 44 | 45 | implementation 'com.squareup.picasso:picasso:2.5.2' 46 | implementation 'com.facebook.fresco:fresco:1.3.0' 47 | implementation 'com.facebook.fresco:animated-gif:1.3.0' 48 | implementation "com.github.bumptech.glide:glide:${_glideVersion}" 49 | implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' 50 | implementation 'id.zelory:compressor:2.1.1' 51 | } 52 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shijingsh/react-native-customized-image-picker/7335f7acde315eec10cf5fa3b5620d048e8b6b91/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Nov 09 16:48:16 GMT+08:00 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/src/main/java/com/mg/app/Compression.java: -------------------------------------------------------------------------------- 1 | package com.mg.app; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.os.Environment; 6 | 7 | import com.facebook.react.bridge.Promise; 8 | import com.facebook.react.bridge.ReadableMap; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileNotFoundException; 13 | import java.io.IOException; 14 | import java.util.Calendar; 15 | import java.util.Random; 16 | 17 | import cn.finalteam.rxgalleryfinal.ui.fragment.MediaGridFragment; 18 | import id.zelory.compressor.Compressor; 19 | 20 | 21 | class Compression { 22 | 23 | public File compressImage(final Activity activity, final ReadableMap options, final String originalImagePath) throws IOException { 24 | Integer maxWidth = options.hasKey("width") ? options.getInt("width") : null; 25 | Integer maxHeight = options.hasKey("height") ? options.getInt("height") : null; 26 | Integer quality = options.hasKey("compressQuality") ? options.getInt("compressQuality") : null; 27 | Integer minCompressSize = options.hasKey("minCompressSize") ? options.getInt("minCompressSize") : null; 28 | 29 | if (maxWidth == null || maxWidth<=0) { 30 | // maxWidth = 300; 31 | } 32 | if (maxHeight == null || maxHeight<=0 ) { 33 | // maxHeight = 300; 34 | } 35 | if (quality == null || quality >= 100 || quality <= 0) { 36 | return new File(originalImagePath); 37 | } 38 | //小于最小压缩大小,就不压缩 39 | if(minCompressSize != null && minCompressSize >0 && getFileSize(new File(originalImagePath)) < minCompressSize * 1024){ 40 | return new File(originalImagePath); 41 | } 42 | String path = MediaGridFragment.getImageStoreDirByStr(); 43 | Compressor compressor = new Compressor(activity) 44 | .setCompressFormat(Bitmap.CompressFormat.JPEG) 45 | .setDestinationDirectoryPath(path+File.separator+"compressed"); 46 | 47 | compressor.setQuality(quality); 48 | 49 | if (maxWidth != null) { 50 | compressor.setMaxWidth(maxWidth); 51 | } 52 | 53 | if (maxHeight != null) { 54 | compressor.setMaxHeight(maxHeight); 55 | } 56 | 57 | File image = new File(originalImagePath); 58 | 59 | String[] paths = image.getName().split("\\.(?=[^\\.]+$)"); 60 | String compressedFileName ="IMG_" +generateRandomFilename() + "_compressed"; 61 | 62 | if(paths.length > 1) 63 | compressedFileName += "." + paths[1]; 64 | 65 | return compressor 66 | .compressToFile(image, compressedFileName); 67 | } 68 | 69 | public String generateRandomFilename(){ 70 | 71 | Random random = new Random(); 72 | int ends = random.nextInt(99); 73 | String randomStr = String.format("%02d",ends); 74 | 75 | Calendar calCurrent = Calendar.getInstance(); 76 | 77 | String now = String.valueOf(calCurrent.getTimeInMillis()); 78 | 79 | return now + randomStr; 80 | } 81 | 82 | synchronized void compressVideo(final Activity activity, final ReadableMap options, 83 | final String originalVideo, final String compressedVideo, final Promise promise) { 84 | // todo: video compression 85 | // failed attempt 1: ffmpeg => slow and licensing issues 86 | promise.resolve(originalVideo); 87 | } 88 | 89 | /** 90 | * 获取指定文件大小 91 | * @param file 92 | * @return 93 | * @throws Exception    94 | */ 95 | public static long getFileSize(File file) { 96 | long size = 0; 97 | if (file.exists()) { 98 | FileInputStream fis = null; 99 | try { 100 | fis = new FileInputStream(file); 101 | size = fis.available(); 102 | } catch (FileNotFoundException e) { 103 | e.printStackTrace(); 104 | } catch (IOException e) { 105 | e.printStackTrace(); 106 | } finally { 107 | try { 108 | fis.close(); 109 | } catch (IOException e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | } 114 | return size; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /android/src/main/java/com/mg/app/PickerModule.java: -------------------------------------------------------------------------------- 1 | package com.mg.app; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.media.MediaMetadataRetriever; 7 | import android.util.Base64; 8 | import android.webkit.MimeTypeMap; 9 | 10 | import com.facebook.react.bridge.Promise; 11 | import com.facebook.react.bridge.ReactApplicationContext; 12 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 13 | import com.facebook.react.bridge.ReactMethod; 14 | import com.facebook.react.bridge.ReadableMap; 15 | import com.facebook.react.bridge.WritableArray; 16 | import com.facebook.react.bridge.WritableMap; 17 | import com.facebook.react.bridge.WritableNativeArray; 18 | import com.facebook.react.bridge.WritableNativeMap; 19 | import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; 20 | import com.nostra13.universalimageloader.core.ImageLoader; 21 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 22 | import com.nostra13.universalimageloader.core.assist.QueueProcessingType; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.FileNotFoundException; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.math.BigDecimal; 31 | import java.util.List; 32 | 33 | import cn.finalteam.rxgalleryfinal.RxGalleryFinal; 34 | import cn.finalteam.rxgalleryfinal.ui.fragment.MediaGridFragment; 35 | import cn.finalteam.rxgalleryfinal.bean.ImageCropBean; 36 | import cn.finalteam.rxgalleryfinal.bean.MediaBean; 37 | import cn.finalteam.rxgalleryfinal.imageloader.ImageLoaderType; 38 | import cn.finalteam.rxgalleryfinal.rxbus.RxBusResultDisposable; 39 | import cn.finalteam.rxgalleryfinal.rxbus.event.ImageMultipleResultEvent; 40 | import cn.finalteam.rxgalleryfinal.rxbus.event.ImageRadioResultEvent; 41 | import cn.finalteam.rxgalleryfinal.ui.RxGalleryListener; 42 | import cn.finalteam.rxgalleryfinal.ui.base.IRadioImageCheckedListener; 43 | 44 | class PickerModule extends ReactContextBaseJavaModule { 45 | private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"; 46 | 47 | private Promise mPickerPromise; 48 | 49 | private boolean cropping = false; 50 | private boolean multiple = false; 51 | private boolean isCamera = false; 52 | private boolean includeBase64 = false; 53 | private boolean openCameraOnStart = false; 54 | private boolean isVideo = false; 55 | private boolean isHidePreview = false; 56 | private boolean isPlayGif = false; 57 | private boolean isHideVideoPreview = false; 58 | private boolean isSelectBoth = false; 59 | private int videoQuality = 1; 60 | private String title = null; 61 | private String imageLoader = null; 62 | //Light Blue 500 63 | private int width = 0; 64 | private int height = 0; 65 | private int maxSize = 9; 66 | private int compressQuality = -1; 67 | private boolean returnAfterShot = false; 68 | private boolean multipleShot = false; 69 | private int spanCount = 3; 70 | private boolean hideCropBottomControls = true; 71 | //宽高比 72 | private float aspectRatioX = 1; 73 | private float aspectRatioY = 1; 74 | private final ReactApplicationContext mReactContext; 75 | 76 | private Compression compression = new Compression(); 77 | private ReadableMap options; 78 | PickerModule(ReactApplicationContext reactContext) { 79 | super(reactContext); 80 | mReactContext = reactContext; 81 | } 82 | 83 | @Override 84 | public String getName() { 85 | return "ImageCropPicker"; 86 | } 87 | 88 | private void setConfiguration(final ReadableMap options) { 89 | multiple = options.hasKey("multiple") && options.getBoolean("multiple"); 90 | isCamera = options.hasKey("isCamera") && options.getBoolean("isCamera"); 91 | openCameraOnStart = options.hasKey("openCameraOnStart") && options.getBoolean("openCameraOnStart"); 92 | width = options.hasKey("width") ? options.getInt("width") : width; 93 | height = options.hasKey("height") ? options.getInt("height") : height; 94 | maxSize = options.hasKey("maxSize") ? options.getInt("maxSize") : (multiple?9:1); 95 | cropping = options.hasKey("cropping") ? options.getBoolean("cropping") : cropping; 96 | includeBase64 = options.hasKey("includeBase64") && options.getBoolean("includeBase64"); 97 | compressQuality = options.hasKey("compressQuality") ? options.getInt("compressQuality") : compressQuality; 98 | title = options.hasKey("title") ? options.getString("title") : title; 99 | returnAfterShot = options.hasKey("returnAfterShot") && options.getBoolean("returnAfterShot"); 100 | multipleShot = options.hasKey("multipleShot") && options.getBoolean("multipleShot"); 101 | isVideo = options.hasKey("isVideo") && options.getBoolean("isVideo"); 102 | isSelectBoth = options.hasKey("isSelectBoth") && options.getBoolean("isSelectBoth"); 103 | videoQuality = options.hasKey("videoQuality") ? options.getInt("videoQuality"):1; 104 | isHidePreview = options.hasKey("isHidePreview") && options.getBoolean("isHidePreview"); 105 | isHideVideoPreview = options.hasKey("isHideVideoPreview") && options.getBoolean("isHideVideoPreview"); 106 | isPlayGif = options.hasKey("isPlayGif") && options.getBoolean("isPlayGif"); 107 | spanCount = options.hasKey("spanCount") ? options.getInt("spanCount") : spanCount; 108 | hideCropBottomControls = options.hasKey("hideCropBottomControls") ? options.getBoolean("hideCropBottomControls") : hideCropBottomControls; 109 | 110 | aspectRatioX = options.hasKey("aspectRatioX") ? options.getInt("aspectRatioX") : aspectRatioX; 111 | aspectRatioY = options.hasKey("aspectRatioY") ? options.getInt("aspectRatioY") : aspectRatioY; 112 | 113 | imageLoader = options.hasKey("imageLoader") ? options.getString("imageLoader") : imageLoader; 114 | this.options = options; 115 | } 116 | 117 | private static String getMimeType(String url) { 118 | String type = null; 119 | String extension = MimeTypeMap.getFileExtensionFromUrl(url); 120 | if (extension != null) { 121 | type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 122 | } 123 | 124 | return type; 125 | } 126 | 127 | private WritableMap getAsyncSelection(final Activity activity,String path) throws Exception { 128 | String mime = getMimeType(path); 129 | if (mime != null && mime.startsWith("video/")) { 130 | return getVideo(activity, path, mime); 131 | } 132 | 133 | return getImage(activity,path); 134 | } 135 | 136 | private WritableMap getAsyncSelection(final Activity activity, ImageCropBean result) throws Exception { 137 | 138 | String path = result.getOriginalPath(); 139 | return getAsyncSelection(activity,path); 140 | } 141 | private WritableMap getAsyncSelection(final Activity activity,MediaBean result) throws Exception { 142 | 143 | String path = result.getOriginalPath(); 144 | return getAsyncSelection(activity,path); 145 | } 146 | 147 | private WritableMap getVideo(Activity activity, String path, String mime) throws Exception { 148 | Bitmap bmp = validateVideo(path); 149 | 150 | WritableMap video = new WritableNativeMap(); 151 | video.putInt("width", bmp.getWidth()); 152 | video.putInt("height", bmp.getHeight()); 153 | video.putString("mime", mime); 154 | video.putInt("size", (int) new File(path).length()); 155 | video.putString("path", "file://" + path); 156 | 157 | return video; 158 | } 159 | 160 | private WritableMap getImage(final Activity activity,String path) throws Exception { 161 | WritableMap image = new WritableNativeMap(); 162 | if (path.startsWith("http://") || path.startsWith("https://")) { 163 | throw new Exception("Cannot select remote files"); 164 | } 165 | validateImage(path); 166 | 167 | // if compression options are provided image will be compressed. If none options is provided, 168 | // then original image will be returned 169 | File compressedImage = compression.compressImage(activity, options, path); 170 | String compressedImagePath = compressedImage.getPath(); 171 | BitmapFactory.Options options = validateImage(compressedImagePath); 172 | 173 | image.putString("path", "file://" + compressedImagePath); 174 | image.putInt("width", options.outWidth); 175 | image.putInt("height", options.outHeight); 176 | image.putString("mime", options.outMimeType); 177 | image.putInt("size", (int) new File(compressedImagePath).length()); 178 | 179 | if (includeBase64) { 180 | image.putString("data", getBase64StringFromFile(compressedImagePath)); 181 | } 182 | 183 | return image; 184 | } 185 | 186 | private WritableMap getImage(final Activity activity, ImageCropBean result) throws Exception { 187 | 188 | String path = result.getOriginalPath(); 189 | return getImage(activity,path); 190 | } 191 | private WritableMap getImage(final Activity activity,MediaBean result) throws Exception { 192 | 193 | String path = result.getOriginalPath(); 194 | return getImage(activity,path); 195 | } 196 | private void initImageLoader(Activity activity) { 197 | 198 | ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(activity); 199 | config.threadPriority(Thread.NORM_PRIORITY - 2); 200 | config.denyCacheImageMultipleSizesInMemory(); 201 | config.diskCacheFileNameGenerator(new Md5FileNameGenerator()); 202 | config.diskCacheSize(50 * 1024 * 1024); // 50 MiB 203 | config.tasksProcessingOrder(QueueProcessingType.LIFO); 204 | ImageLoader.getInstance().init(config.build()); 205 | } 206 | 207 | private void initCrop(RxGalleryFinal rxGalleryFinal){ 208 | //设置剪切最大宽度,高度 209 | if(this.width>0 && this.height>0){ 210 | rxGalleryFinal.cropMaxResultSize(this.width,this.height); 211 | } 212 | //用户设置的宽高比优先 213 | if(aspectRatioX>1||aspectRatioY>1){ 214 | rxGalleryFinal.cropWithAspectRatio(aspectRatioX,aspectRatioY); 215 | rxGalleryFinal.cropFreeStyleCropEnabled(true); 216 | }else { 217 | if(this.width>0 && this.height>0){ 218 | //x=宽,y=高 219 | if(this.width>this.height){ 220 | float f = new BigDecimal(this.width).divide(new BigDecimal(this.height),1,BigDecimal.ROUND_HALF_UP).floatValue(); 221 | rxGalleryFinal.cropWithAspectRatio(f*10,10); 222 | }else { 223 | float f = new BigDecimal(this.height).divide(new BigDecimal(this.width),1,BigDecimal.ROUND_HALF_UP).floatValue(); 224 | rxGalleryFinal.cropWithAspectRatio(10,f*10); 225 | } 226 | 227 | rxGalleryFinal.cropFreeStyleCropEnabled(false); 228 | }else{ 229 | rxGalleryFinal.cropWithAspectRatio(aspectRatioX,aspectRatioY); 230 | rxGalleryFinal.cropFreeStyleCropEnabled(true); 231 | } 232 | } 233 | rxGalleryFinal.cropHideBottomControls(this.hideCropBottomControls); 234 | } 235 | 236 | @ReactMethod 237 | public void openPicker(final ReadableMap options, final Promise promise) { 238 | final Activity activity = getCurrentActivity(); 239 | 240 | if (activity == null) { 241 | promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist"); 242 | return; 243 | } 244 | 245 | setConfiguration(options); 246 | initImageLoader(activity); 247 | mPickerPromise = promise; 248 | 249 | RxGalleryFinal rxGalleryFinal = RxGalleryFinal.with(activity); 250 | rxGalleryFinal.spanCount(spanCount); 251 | rxGalleryFinal.setVideoQuality(videoQuality); 252 | if(openCameraOnStart){ 253 | rxGalleryFinal.openCameraOnStart(); 254 | }else if(!isCamera){ 255 | rxGalleryFinal.hideCamera(); 256 | } 257 | if(compressQuality>0){ 258 | rxGalleryFinal.cropropCompressionQuality(compressQuality); 259 | } 260 | if(title != null){ 261 | rxGalleryFinal.setTitle(title); 262 | } 263 | if(returnAfterShot){ 264 | rxGalleryFinal.returnAfterShot(); 265 | } 266 | if(multipleShot){ 267 | rxGalleryFinal.multipleShot(); 268 | } 269 | if(isVideo){ 270 | rxGalleryFinal.video(); 271 | }else { 272 | rxGalleryFinal.image(); 273 | } 274 | if(isSelectBoth){ 275 | rxGalleryFinal.selectBoth(); 276 | } 277 | if(isHidePreview){ 278 | rxGalleryFinal.hidePreview(); 279 | } 280 | if (isHideVideoPreview){ 281 | rxGalleryFinal.videoPreview(); 282 | } 283 | if(isPlayGif){ 284 | rxGalleryFinal.gif(); 285 | } 286 | if (imageLoader != null){ 287 | switch (imageLoader){ 288 | case "PICASSO": 289 | rxGalleryFinal.imageLoader(ImageLoaderType.PICASSO); 290 | break; 291 | case "GLIDE": 292 | rxGalleryFinal.imageLoader(ImageLoaderType.GLIDE); 293 | break; 294 | case "FRESCO": 295 | rxGalleryFinal.imageLoader(ImageLoaderType.FRESCO); 296 | break; 297 | case "UNIVERSAL": 298 | rxGalleryFinal.imageLoader(ImageLoaderType.UNIVERSAL); 299 | break; 300 | default: 301 | break; 302 | } 303 | }else{ 304 | rxGalleryFinal.imageLoader(ImageLoaderType.GLIDE); 305 | } 306 | if(!this.multiple) { 307 | if(cropping){ 308 | rxGalleryFinal.crop(); 309 | initCrop(rxGalleryFinal); 310 | //裁剪图片的回调 311 | RxGalleryListener 312 | .getInstance() 313 | .setRadioImageCheckedListener( 314 | new IRadioImageCheckedListener() { 315 | @Override 316 | public void cropAfter(Object t) { 317 | WritableArray resultArr = new WritableNativeArray(); 318 | try { 319 | if(t!=null) 320 | resultArr.pushMap(getAsyncSelection(activity,t.toString())); 321 | } catch (Exception e) { 322 | e.printStackTrace(); 323 | } 324 | if(resultArr.size()==0){ 325 | mPickerPromise.reject("cancel","cancel"); 326 | }else{ 327 | mPickerPromise.resolve(resultArr); 328 | } 329 | } 330 | 331 | @Override 332 | public boolean isActivityFinish() { 333 | return true; 334 | } 335 | }); 336 | } 337 | rxGalleryFinal 338 | .radio() 339 | .subscribe(new RxBusResultDisposable() { 340 | @Override 341 | protected void onEvent(ImageRadioResultEvent imageRadioResultEvent) throws Exception { 342 | if(!cropping){ 343 | ImageCropBean result = imageRadioResultEvent.getResult(); 344 | WritableArray resultArr = new WritableNativeArray(); 345 | if(result!=null) 346 | resultArr.pushMap(getAsyncSelection(activity,result)); 347 | if(resultArr.size()==0){ 348 | mPickerPromise.reject("cancel","cancel"); 349 | }else{ 350 | mPickerPromise.resolve(resultArr); 351 | } 352 | } 353 | } 354 | }) 355 | .openGallery(); 356 | } else { 357 | rxGalleryFinal 358 | .multiple() 359 | .maxSize(maxSize) 360 | .subscribe(new RxBusResultDisposable() { 361 | @Override 362 | protected void onEvent(ImageMultipleResultEvent imageMultipleResultEvent) throws Exception { 363 | List list = imageMultipleResultEvent.getResult(); 364 | WritableArray resultArr = new WritableNativeArray(); 365 | for(MediaBean bean:list){ 366 | if(bean!=null) 367 | resultArr.pushMap(getAsyncSelection(activity,bean)); 368 | } 369 | if(resultArr.size()==0){ 370 | mPickerPromise.reject("cancel","cancel"); 371 | }else{ 372 | mPickerPromise.resolve(resultArr); 373 | } 374 | } 375 | 376 | @Override 377 | public void onComplete() { 378 | super.onComplete(); 379 | } 380 | }) 381 | .openGallery(); 382 | } 383 | } 384 | 385 | @ReactMethod 386 | public void clean(final Promise promise) { 387 | String path = MediaGridFragment.getImageStoreDirByStr(); 388 | File file = new File(path); 389 | 390 | deleteRecursive(file); 391 | promise.resolve(null); 392 | } 393 | 394 | @ReactMethod 395 | public void openCamera(final ReadableMap options, final Promise promise) { 396 | { 397 | final Activity activity = getCurrentActivity(); 398 | 399 | if (activity == null) { 400 | promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist"); 401 | return; 402 | } 403 | 404 | setConfiguration(options); 405 | initImageLoader(activity); 406 | mPickerPromise = promise; 407 | 408 | RxGalleryFinal rxGalleryFinal = RxGalleryFinal.with(activity); 409 | rxGalleryFinal.setVideoQuality(videoQuality); 410 | if(compressQuality>0){ 411 | rxGalleryFinal.cropropCompressionQuality(compressQuality); 412 | } 413 | rxGalleryFinal.openCameraOnStart(); 414 | rxGalleryFinal.returnAfterShot(); 415 | if(isVideo){ 416 | rxGalleryFinal.video(); 417 | }else { 418 | rxGalleryFinal.image(); 419 | } 420 | if(isSelectBoth){ 421 | rxGalleryFinal.selectBoth(); 422 | } 423 | if(isPlayGif){ 424 | rxGalleryFinal.gif(); 425 | } 426 | if (imageLoader != null){ 427 | switch (imageLoader){ 428 | case "PICASSO": 429 | rxGalleryFinal.imageLoader(ImageLoaderType.PICASSO); 430 | break; 431 | case "GLIDE": 432 | rxGalleryFinal.imageLoader(ImageLoaderType.GLIDE); 433 | break; 434 | case "FRESCO": 435 | rxGalleryFinal.imageLoader(ImageLoaderType.FRESCO); 436 | break; 437 | case "UNIVERSAL": 438 | rxGalleryFinal.imageLoader(ImageLoaderType.UNIVERSAL); 439 | break; 440 | default: 441 | break; 442 | } 443 | }else{ 444 | rxGalleryFinal.imageLoader(ImageLoaderType.GLIDE); 445 | } 446 | if(!this.multiple) { 447 | if(cropping){ 448 | rxGalleryFinal.crop(); 449 | initCrop(rxGalleryFinal); 450 | //裁剪图片的回调 451 | RxGalleryListener 452 | .getInstance() 453 | .setRadioImageCheckedListener( 454 | new IRadioImageCheckedListener() { 455 | @Override 456 | public void cropAfter(Object t) { 457 | WritableArray resultArr = new WritableNativeArray(); 458 | try { 459 | if(t!=null) 460 | resultArr.pushMap(getAsyncSelection(activity,t.toString())); 461 | } catch (Exception e) { 462 | e.printStackTrace(); 463 | } 464 | if(resultArr.size()==0){ 465 | mPickerPromise.reject("cancel","cancel"); 466 | }else{ 467 | mPickerPromise.resolve(resultArr); 468 | } 469 | } 470 | 471 | @Override 472 | public boolean isActivityFinish() { 473 | return true; 474 | } 475 | }); 476 | } 477 | rxGalleryFinal 478 | .radio() 479 | .subscribe(new RxBusResultDisposable() { 480 | @Override 481 | protected void onEvent(ImageRadioResultEvent imageRadioResultEvent) throws Exception { 482 | if(!cropping){ 483 | ImageCropBean result = imageRadioResultEvent.getResult(); 484 | WritableArray resultArr = new WritableNativeArray(); 485 | if(result!=null) 486 | resultArr.pushMap(getAsyncSelection(activity,result)); 487 | if(resultArr.size()==0){ 488 | mPickerPromise.reject("cancel","cancel"); 489 | }else{ 490 | mPickerPromise.resolve(resultArr); 491 | } 492 | } 493 | } 494 | }) 495 | .openGallery(); 496 | } 497 | } 498 | } 499 | private String getBase64StringFromFile(String absoluteFilePath) { 500 | InputStream inputStream; 501 | 502 | try { 503 | inputStream = new FileInputStream(new File(absoluteFilePath)); 504 | } catch (FileNotFoundException e) { 505 | e.printStackTrace(); 506 | return null; 507 | } 508 | 509 | byte[] bytes; 510 | byte[] buffer = new byte[8192]; 511 | int bytesRead; 512 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 513 | 514 | try { 515 | while ((bytesRead = inputStream.read(buffer)) != -1) { 516 | output.write(buffer, 0, bytesRead); 517 | } 518 | } catch (IOException e) { 519 | e.printStackTrace(); 520 | } 521 | 522 | bytes = output.toByteArray(); 523 | return Base64.encodeToString(bytes, Base64.NO_WRAP); 524 | } 525 | 526 | 527 | private BitmapFactory.Options validateImage(String path) throws Exception { 528 | BitmapFactory.Options options = new BitmapFactory.Options(); 529 | options.inJustDecodeBounds = true; 530 | options.inPreferredConfig = Bitmap.Config.RGB_565; 531 | options.inDither = true; 532 | 533 | BitmapFactory.decodeFile(path, options); 534 | 535 | if (options.outMimeType == null || options.outWidth == 0 || options.outHeight == 0) { 536 | throw new Exception("Invalid image selected"); 537 | } 538 | 539 | return options; 540 | } 541 | 542 | private Bitmap validateVideo(String path) throws Exception { 543 | MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 544 | retriever.setDataSource(path); 545 | Bitmap bmp = retriever.getFrameAtTime(); 546 | 547 | if (bmp == null) { 548 | throw new Exception("Cannot retrieve video data"); 549 | } 550 | 551 | return bmp; 552 | } 553 | 554 | private void deleteRecursive(File fileOrDirectory) { 555 | if (fileOrDirectory.isDirectory()) { 556 | for (File child : fileOrDirectory.listFiles()) { 557 | deleteRecursive(child); 558 | } 559 | } 560 | 561 | fileOrDirectory.delete(); 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /android/src/main/java/com/mg/app/PickerPackage.java: -------------------------------------------------------------------------------- 1 | package com.mg.app; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class PickerPackage implements ReactPackage { 14 | 15 | public List> createJSModules() { 16 | return Collections.emptyList(); 17 | } 18 | 19 | public List createViewManagers(ReactApplicationContext reactContext) { 20 | return Collections.emptyList(); 21 | } 22 | 23 | public List createNativeModules(ReactApplicationContext reactContext) { 24 | List modules = new ArrayList<>(); 25 | modules.add(new PickerModule(reactContext)); 26 | 27 | return modules; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shijingsh/react-native-customized-image-picker/7335f7acde315eec10cf5fa3b5620d048e8b6b91/android/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #FFFFFF 6 | #FFFFFF 7 | #000000 8 | 9 | #2196F3 10 | #1976D2 11 | #FF4081 12 | 13 | #FF5722 14 | #E64A19 15 | #FF4081 16 | 17 | #00BCD4 18 | #0097A7 19 | #FF4081 20 | 21 | #4CAF50 22 | #388E3C 23 | #FF4081 24 | 25 | #009688 26 | #00796B 27 | #FF4081 28 | 29 | #da4336 30 | #b93221 31 | #3D5AFE 32 | 33 | #E91E63 34 | #C2185B 35 | #3D5AFE 36 | 37 | #673AB7 38 | #512DA8 39 | #FF4081 40 | 41 | #616161 42 | #424242 43 | #00E676 44 | 45 | #2A2A2F 46 | #151515 47 | #FF4081 48 | 49 | #785447 50 | #5C3F36 51 | #785447 52 | 53 | -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | app_name 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 20 | 21 | 27 | 28 | 33 | 34 | 39 | 40 | 45 | 46 | 51 | 52 | 57 | 58 | 63 | 64 | 69 | 70 | 75 | 76 | 81 | 82 | 87 | 88 | -------------------------------------------------------------------------------- /images/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shijingsh/react-native-customized-image-picker/7335f7acde315eec10cf5fa3b5620d048e8b6b91/images/pic.png -------------------------------------------------------------------------------- /images/pic2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shijingsh/react-native-customized-image-picker/7335f7acde315eec10cf5fa3b5620d048e8b6b91/images/pic2.png -------------------------------------------------------------------------------- /images/pic3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shijingsh/react-native-customized-image-picker/7335f7acde315eec10cf5fa3b5620d048e8b6b91/images/pic3.png -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shijingsh/react-native-customized-image-picker/7335f7acde315eec10cf5fa3b5620d048e8b6b91/index.android.js -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {NativeModules} from 'react-native'; 4 | export default NativeModules.ImageCropPicker; 5 | -------------------------------------------------------------------------------- /ios/ImageCropPicker.h: -------------------------------------------------------------------------------- 1 | 2 | #if __has_include("RCTBridgeModule.h") 3 | #import "RCTBridgeModule.h" 4 | #else 5 | #import 6 | #endif 7 | #import 8 | #import "TZImagePickerController.h" 9 | @interface ImageCropPicker : NSObject 10 | 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /ios/ImageCropPicker.m: -------------------------------------------------------------------------------- 1 | 2 | #import "ImageCropPicker.h" 3 | 4 | #import "TZImageManager.h" 5 | #import "NSDictionary+SYSafeConvert.h" 6 | #import "TZImageCropManager.h" 7 | #import 8 | #import 9 | 10 | @interface ImageCropPicker () 11 | 12 | @property (nonatomic, strong) UIImagePickerController *imagePickerVc; 13 | @property (nonatomic, strong) NSDictionary *cameraOptions; 14 | /** 15 | 保存Promise的resolve block 16 | */ 17 | @property (nonatomic, copy) RCTPromiseResolveBlock resolveBlock; 18 | /** 19 | 保存Promise的reject block 20 | */ 21 | @property (nonatomic, copy) RCTPromiseRejectBlock rejectBlock; 22 | 23 | /** 24 | 保存选中的图片数组 25 | */ 26 | @property (nonatomic, strong) NSMutableArray *selectedAssets; 27 | @end 28 | 29 | @implementation ImageCropPicker 30 | 31 | - (instancetype)init { 32 | self = [super init]; 33 | if (self) { 34 | _selectedAssets = [NSMutableArray array]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)dealloc { 40 | _selectedAssets = nil; 41 | } 42 | 43 | RCT_EXPORT_MODULE() 44 | 45 | 46 | RCT_REMAP_METHOD(openPicker, 47 | options:(NSDictionary *)options 48 | showImagePickerResolver:(RCTPromiseResolveBlock)resolve 49 | rejecter:(RCTPromiseRejectBlock)reject) { 50 | self.cameraOptions = options; 51 | self.resolveBlock = resolve; 52 | self.rejectBlock = reject; 53 | 54 | BOOL isVideo = [options sy_boolForKey:@"isVideo"] || NO; 55 | if(!isVideo){ 56 | [self openImagePicker]; 57 | }else{ 58 | [self openVideoPicker]; 59 | } 60 | 61 | 62 | } 63 | 64 | RCT_REMAP_METHOD(openCamera, 65 | options:(NSDictionary *)options 66 | openCameraResolver:(RCTPromiseResolveBlock)resolve 67 | rejecter:(RCTPromiseRejectBlock)reject) { 68 | self.cameraOptions = options; 69 | self.resolveBlock = resolve; 70 | self.rejectBlock = reject; 71 | [self takePhoto]; 72 | } 73 | 74 | RCT_REMAP_METHOD(clean, 75 | cleanResolver:(RCTPromiseResolveBlock)resolve 76 | rejecter:(RCTPromiseRejectBlock)reject) { 77 | NSFileManager *fileManager = [NSFileManager defaultManager]; 78 | [fileManager removeItemAtPath: [NSString stringWithFormat:@"%@ImageCropPicker", NSTemporaryDirectory()] error:nil]; 79 | 80 | resolve(nil); 81 | } 82 | 83 | 84 | - (void)openVideoPicker { 85 | NSInteger maxSize = [self.cameraOptions sy_integerForKey:@"maxSize"]; 86 | BOOL isCamera = [self.cameraOptions sy_boolForKey:@"isCamera"]; 87 | BOOL cropping = [self.cameraOptions sy_boolForKey:@"cropping"]; 88 | BOOL isGif = [self.cameraOptions sy_boolForKey:@"isGif"]; 89 | BOOL allowPickingVideo = [self.cameraOptions sy_boolForKey:@"allowPickingVideo"]; 90 | BOOL allowPickingMultipleVideo = [self.cameraOptions sy_boolForKey:@"allowPickingMultipleVideo"]; 91 | BOOL allowPickingImage = [self.cameraOptions sy_boolForKey:@"allowPickingImage"]; 92 | BOOL allowTakeVideo = [self.cameraOptions sy_boolForKey:@"allowTakeVideo"]; 93 | BOOL showCropCircle = [self.cameraOptions sy_boolForKey:@"showCropCircle"]; 94 | BOOL isRecordSelected = [self.cameraOptions sy_boolForKey:@"isRecordSelected"]; 95 | BOOL allowPickingOriginalPhoto = [self.cameraOptions sy_boolForKey:@"allowPickingOriginalPhoto"]; 96 | BOOL sortAscendingByModificationDate = [self.cameraOptions sy_boolForKey:@"sortAscendingByModificationDate"]; 97 | BOOL showSelectedIndex = [self.cameraOptions sy_boolForKey:@"showSelectedIndex"]; 98 | BOOL multiple = [self.cameraOptions sy_boolForKey:@"multiple"]; 99 | NSInteger CropW = [self.cameraOptions sy_integerForKey:@"CropW"]; 100 | NSInteger CropH = [self.cameraOptions sy_integerForKey:@"CropH"]; 101 | NSInteger circleCropRadius = [self.cameraOptions sy_integerForKey:@"circleCropRadius"]; 102 | NSInteger videoMaximumDuration = [self.cameraOptions sy_integerForKey:@"videoMaximumDuration"]; 103 | NSInteger compressQuality = [self.cameraOptions sy_integerForKey:@"compressQuality"]; 104 | BOOL isVideo = YES; 105 | if(multiple){ 106 | maxSize = maxSize?maxSize:9; 107 | }else{ 108 | maxSize = maxSize?maxSize:1; 109 | } 110 | if(!CropW){ 111 | CropW = [self.cameraOptions sy_integerForKey:@"width"]; 112 | } 113 | if(!CropW){ 114 | CropW = 300; 115 | } 116 | if(!CropH){ 117 | CropH = [self.cameraOptions sy_integerForKey:@"height"]; 118 | } 119 | if(!CropH){ 120 | CropH = 300; 121 | } 122 | TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:maxSize delegate:self]; 123 | 124 | imagePickerVc.maxImagesCount = maxSize; 125 | imagePickerVc.allowPickingGif = isGif; // 允许GIF 126 | imagePickerVc.allowTakePicture = isCamera; // 允许用户在内部拍照 127 | imagePickerVc.allowPickingVideo = isVideo; // 不允许视频 128 | imagePickerVc.allowPickingImage = allowPickingImage; 129 | imagePickerVc.allowTakeVideo = isCamera; // 允许拍摄视频 130 | imagePickerVc.videoMaximumDuration = videoMaximumDuration; 131 | imagePickerVc.allowPickingMultipleVideo = multiple; 132 | imagePickerVc.allowPickingOriginalPhoto = allowPickingOriginalPhoto; // 允许原图 133 | imagePickerVc.sortAscendingByModificationDate = sortAscendingByModificationDate; 134 | imagePickerVc.alwaysEnableDoneBtn = YES; 135 | imagePickerVc.allowCrop = cropping; // 裁剪 136 | imagePickerVc.autoDismiss = NO; 137 | imagePickerVc.showSelectedIndex = showSelectedIndex; 138 | imagePickerVc.modalPresentationStyle = UIModalPresentationFullScreen; 139 | 140 | if (isRecordSelected) { 141 | imagePickerVc.selectedAssets = self.selectedAssets; // 当前已选中的图片 142 | } 143 | 144 | if (maxSize == 1) { 145 | // 单选模式 146 | imagePickerVc.showSelectBtn = NO; 147 | 148 | if(cropping){ 149 | if(showCropCircle) { 150 | imagePickerVc.needCircleCrop = showCropCircle; //圆形裁剪 151 | imagePickerVc.circleCropRadius = circleCropRadius; //圆形半径 152 | } else { 153 | CGFloat x = ([[UIScreen mainScreen] bounds].size.width - CropW) / 2; 154 | CGFloat y = ([[UIScreen mainScreen] bounds].size.height - CropH) / 2; 155 | imagePickerVc.cropRect = CGRectMake(x,y,CropW,CropH); 156 | } 157 | } 158 | } 159 | 160 | 161 | __weak TZImagePickerController *weakPicker = imagePickerVc; 162 | // Documentation source = through comments in TZImagePicker. 163 | // If multiple videos are selected, this callback is called. 164 | [imagePickerVc setDidFinishPickingPhotosWithInfosHandle:^(NSArray *photos,NSArray *assets,BOOL isSelectOriginalPhoto,NSArray *infos) { 165 | [weakPicker showProgressHUD]; 166 | [self handleAssets:assets photos:photos compressQuality:compressQuality isSelectOriginalPhoto:isSelectOriginalPhoto completion:^(NSArray *selecteds) { 167 | 168 | [self invokeSuccessWithResult:selecteds]; 169 | [weakPicker dismissViewControllerAnimated:YES completion:nil]; 170 | [weakPicker hideProgressHUD]; 171 | } fail:^(NSError *error) { 172 | [weakPicker dismissViewControllerAnimated:YES completion:nil]; 173 | [weakPicker hideProgressHUD]; 174 | }]; 175 | }]; 176 | 177 | 178 | // This callback is called only when picking SINGLE video. 179 | [imagePickerVc setDidFinishPickingVideoHandle:^(UIImage *coverImage, PHAsset *asset) { 180 | [weakPicker showProgressHUD]; 181 | // TODO: someshow change presetName based on provided compressQuality? 182 | // Does it even matter here? 183 | [[TZImageManager manager] getVideoOutputPathWithAsset:asset presetName:AVAssetExportPresetHighestQuality success:^(NSString *outputPath) { 184 | NSLog(@"视频导出成功:%@", outputPath); 185 | [self invokeSuccessWithResult:@[[self handleVideoData:outputPath asset:asset coverImage:coverImage compressQuality:compressQuality]]]; 186 | [weakPicker dismissViewControllerAnimated:YES completion:nil]; 187 | [weakPicker hideProgressHUD]; 188 | } failure:^(NSString *errorMessage, NSError *error) { 189 | NSLog(@"视频导出失败:%@,error:%@",errorMessage, error); 190 | [weakPicker dismissViewControllerAnimated:YES completion:nil]; 191 | [weakPicker hideProgressHUD]; 192 | }]; 193 | }]; 194 | 195 | [imagePickerVc setImagePickerControllerDidCancelHandle:^{ 196 | [weakPicker dismissViewControllerAnimated:YES completion:nil]; 197 | [self invokeError]; 198 | [weakPicker hideProgressHUD]; 199 | }]; 200 | 201 | [[self topViewController] presentViewController:imagePickerVc animated:YES completion:nil]; 202 | } 203 | 204 | - (void)openImagePicker { 205 | // 照片最大可选张数 206 | NSInteger maxSize = [self.cameraOptions sy_integerForKey:@"maxSize"]; 207 | // 显示内部拍照按钮 208 | BOOL isCamera = [self.cameraOptions sy_boolForKey:@"isCamera"]; 209 | BOOL cropping = [self.cameraOptions sy_boolForKey:@"cropping"]; 210 | BOOL isGif = [self.cameraOptions sy_boolForKey:@"isGif"]; 211 | BOOL showCropCircle = [self.cameraOptions sy_boolForKey:@"showCropCircle"]; 212 | BOOL isRecordSelected = [self.cameraOptions sy_boolForKey:@"isRecordSelected"]; 213 | BOOL allowPickingOriginalPhoto = [self.cameraOptions sy_boolForKey:@"allowPickingOriginalPhoto"]; 214 | BOOL allowPickingMultipleVideo = [self.cameraOptions sy_boolForKey:@"allowPickingMultipleVideo"]; 215 | BOOL sortAscendingByModificationDate = [self.cameraOptions sy_boolForKey:@"sortAscendingByModificationDate"]; 216 | BOOL showSelectedIndex = [self.cameraOptions sy_boolForKey:@"showSelectedIndex"]; 217 | BOOL multiple = [self.cameraOptions sy_boolForKey:@"multiple"]; 218 | NSInteger CropW = [self.cameraOptions sy_integerForKey:@"CropW"]; 219 | NSInteger CropH = [self.cameraOptions sy_integerForKey:@"CropH"]; 220 | NSInteger circleCropRadius = [self.cameraOptions sy_integerForKey:@"circleCropRadius"]; 221 | NSInteger compressQuality = [self.cameraOptions sy_integerForKey:@"compressQuality"]; 222 | if(multiple){ 223 | maxSize = maxSize?maxSize:9; 224 | }else{ 225 | maxSize = maxSize?maxSize:1; 226 | } 227 | if(!CropW){ 228 | CropW = [self.cameraOptions sy_integerForKey:@"width"]; 229 | } 230 | if(!CropW){ 231 | CropW = 300; 232 | } 233 | if(!CropH){ 234 | CropH = [self.cameraOptions sy_integerForKey:@"height"]; 235 | } 236 | if(!CropH){ 237 | CropH = 300; 238 | } 239 | TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:maxSize delegate:self]; 240 | 241 | imagePickerVc.maxImagesCount = maxSize; 242 | imagePickerVc.allowPickingGif = isGif; // 允许GIF 243 | imagePickerVc.allowTakePicture = isCamera; // 允许用户在内部拍照 244 | imagePickerVc.allowPickingVideo = NO; // 不允许视频 245 | imagePickerVc.showSelectedIndex = showSelectedIndex; 246 | imagePickerVc.allowPickingOriginalPhoto = allowPickingOriginalPhoto; // 允许原图 247 | imagePickerVc.sortAscendingByModificationDate = sortAscendingByModificationDate; 248 | imagePickerVc.alwaysEnableDoneBtn = YES; 249 | imagePickerVc.allowPickingMultipleVideo = isGif ? YES : allowPickingMultipleVideo; 250 | imagePickerVc.allowCrop = cropping; // 裁剪 251 | imagePickerVc.modalPresentationStyle = UIModalPresentationFullScreen; 252 | 253 | if (isRecordSelected) { 254 | imagePickerVc.selectedAssets = self.selectedAssets; // 当前已选中的图片 255 | } 256 | 257 | if (maxSize == 1) { 258 | // 单选模式 259 | imagePickerVc.showSelectBtn = NO; 260 | 261 | if(cropping){ 262 | if(showCropCircle) { 263 | imagePickerVc.needCircleCrop = showCropCircle; //圆形裁剪 264 | imagePickerVc.circleCropRadius = circleCropRadius; //圆形半径 265 | } else { 266 | CGFloat x = ([[UIScreen mainScreen] bounds].size.width - CropW) / 2; 267 | CGFloat y = ([[UIScreen mainScreen] bounds].size.height - CropH) / 2; 268 | imagePickerVc.cropRect = CGRectMake(x,y,CropW,CropH); 269 | } 270 | } 271 | } 272 | 273 | __weak TZImagePickerController *weakPicker = imagePickerVc; 274 | [imagePickerVc setDidFinishPickingPhotosWithInfosHandle:^(NSArray *photos,NSArray *assets,BOOL isSelectOriginalPhoto,NSArray *infos) { 275 | if (isRecordSelected) { 276 | self.selectedAssets = [NSMutableArray arrayWithArray:assets]; 277 | } 278 | [weakPicker showProgressHUD]; 279 | if (maxSize == 1 && cropping) { 280 | [self invokeSuccessWithResult:@[[self handleCropImage:photos[0] phAsset:assets[0] compressQuality:compressQuality]]]; 281 | } else { 282 | [infos enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 283 | [self handleAssets:assets photos:photos compressQuality:compressQuality isSelectOriginalPhoto:isSelectOriginalPhoto completion:^(NSArray *selecteds) { 284 | [self invokeSuccessWithResult:selecteds]; 285 | } fail:^(NSError *error) { 286 | 287 | }]; 288 | }]; 289 | } 290 | [weakPicker hideProgressHUD]; 291 | }]; 292 | 293 | __weak TZImagePickerController *weakPickerVc = imagePickerVc; 294 | [imagePickerVc setImagePickerControllerDidCancelHandle:^{ 295 | [self invokeError]; 296 | [weakPickerVc hideProgressHUD]; 297 | }]; 298 | 299 | [[self topViewController] presentViewController:imagePickerVc animated:YES completion:nil]; 300 | } 301 | 302 | - (UIImagePickerController *)imagePickerVc { 303 | if (_imagePickerVc == nil) { 304 | _imagePickerVc = [[UIImagePickerController alloc] init]; 305 | _imagePickerVc.delegate = self; 306 | } 307 | return _imagePickerVc; 308 | } 309 | 310 | #pragma mark - UIImagePickerController 311 | - (void)takePhoto { 312 | AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; 313 | NSString * cancelStr = [NSBundle tz_localizedStringForKey:@"Cancel"]; 314 | NSString * settingStr = [NSBundle tz_localizedStringForKey:@"Setting"]; 315 | 316 | NSDictionary *infoDict = [TZCommonTools tz_getInfoDictionary]; 317 | NSString *appName = [infoDict valueForKey:@"CFBundleDisplayName"]; 318 | if (!appName) appName = [infoDict valueForKey:@"CFBundleName"]; 319 | if (!appName) appName = [infoDict valueForKey:@"CFBundleExecutable"]; 320 | 321 | NSString *title = [NSBundle tz_localizedStringForKey:@"Can not use camera"]; 322 | NSString *message = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\""],appName]; 323 | 324 | if (authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied) { 325 | // 无相机权限 做一个友好的提示 326 | 327 | UIAlertView * alert = [[UIAlertView alloc]initWithTitle:title message:message delegate:self cancelButtonTitle:cancelStr otherButtonTitles:settingStr, nil]; 328 | [alert show]; 329 | } else if (authStatus == AVAuthorizationStatusNotDetermined) { 330 | // fix issue 466, 防止用户首次拍照拒绝授权时相机页黑屏 331 | [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { 332 | if (granted) { 333 | dispatch_async(dispatch_get_main_queue(), ^{ 334 | [self takePhoto]; 335 | }); 336 | } 337 | }]; 338 | // 拍照之前还需要检查相册权限 339 | } else if ([PHPhotoLibrary authorizationStatus] == 2) { // 已被拒绝,没有相册权限,将无法保存拍的照片 340 | UIAlertView * alert = [[UIAlertView alloc]initWithTitle:title message:message delegate:self cancelButtonTitle:cancelStr otherButtonTitles:settingStr, nil]; 341 | [alert show]; 342 | } else if ([PHPhotoLibrary authorizationStatus] == 0) { // 未请求过相册权限 343 | [[TZImageManager manager] requestAuthorizationWithCompletion:^{ 344 | [self takePhoto]; 345 | }]; 346 | } else { 347 | [self pushImagePickerController]; 348 | } 349 | } 350 | 351 | // 调用相机 352 | - (void)pushImagePickerController { 353 | UIImagePickerControllerSourceType sourceType = UIImagePickerControllerSourceTypeCamera; 354 | if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) { 355 | self.imagePickerVc.sourceType = sourceType; 356 | [[self topViewController] presentViewController:self.imagePickerVc animated:YES completion:nil]; 357 | } else { 358 | NSLog(@"模拟器中无法打开照相机,请在真机中使用"); 359 | } 360 | } 361 | 362 | - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 363 | [picker dismissViewControllerAnimated:YES completion:^{ 364 | NSString *type = [info objectForKey:UIImagePickerControllerMediaType]; 365 | if ([type isEqualToString:@"public.image"]) { 366 | 367 | TZImagePickerController *tzImagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:nil]; 368 | tzImagePickerVc.sortAscendingByModificationDate = NO; 369 | [tzImagePickerVc showProgressHUD]; 370 | UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; 371 | 372 | // save photo and get asset / 保存图片,获取到asset 373 | [[TZImageManager manager] savePhotoWithImage:image location:NULL completion:^(PHAsset *asset, NSError *error){ 374 | if (error) { 375 | [tzImagePickerVc hideProgressHUD]; 376 | NSLog(@"图片保存失败 %@",error); 377 | } else { 378 | [tzImagePickerVc hideProgressHUD]; 379 | 380 | TZAssetModel *assetModel = [[TZImageManager manager] createModelWithAsset:asset]; 381 | BOOL cropping = [self.cameraOptions sy_boolForKey:@"cropping"]; 382 | BOOL showCropCircle = [self.cameraOptions sy_boolForKey:@"showCropCircle"]; 383 | NSInteger CropW = [self.cameraOptions sy_integerForKey:@"CropW"]; 384 | NSInteger CropH = [self.cameraOptions sy_integerForKey:@"CropH"]; 385 | NSInteger circleCropRadius = [self.cameraOptions sy_integerForKey:@"circleCropRadius"]; 386 | NSInteger compressQuality = [self.cameraOptions sy_integerForKey:@"compressQuality"]; 387 | 388 | if (cropping) { 389 | TZImagePickerController *imagePicker = [[TZImagePickerController alloc] initCropTypeWithAsset:assetModel.asset photo:image completion:^(UIImage *cropImage, id asset) { 390 | [self invokeSuccessWithResult:@[[self handleCropImage:cropImage phAsset:asset compressQuality:compressQuality]]]; 391 | }]; 392 | imagePicker.allowPickingImage = YES; 393 | if(showCropCircle) { 394 | imagePicker.needCircleCrop = showCropCircle; //圆形裁剪 395 | imagePicker.circleCropRadius = circleCropRadius; //圆形半径 396 | } else { 397 | CGFloat x = ([[UIScreen mainScreen] bounds].size.width - CropW) / 2; 398 | CGFloat y = ([[UIScreen mainScreen] bounds].size.height - CropH) / 2; 399 | imagePicker.cropRect = CGRectMake(x,y,CropW,CropH); 400 | } 401 | [[self topViewController] presentViewController:imagePicker animated:YES completion:nil]; 402 | } else { 403 | [self invokeSuccessWithResult:@[[self handleCropImage:image phAsset:asset compressQuality:compressQuality]]]; 404 | } 405 | } 406 | }]; 407 | } 408 | }]; 409 | } 410 | 411 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { 412 | [self invokeError]; 413 | if ([picker isKindOfClass:[UIImagePickerController class]]) { 414 | [picker dismissViewControllerAnimated:YES completion:nil]; 415 | } 416 | } 417 | 418 | #pragma mark - UIAlertViewDelegate 419 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 420 | if (buttonIndex == 1) { // 去设置界面,开启相机访问权限 421 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; 422 | } 423 | } 424 | 425 | - (BOOL)isAssetCanSelect:(PHAsset *)asset { 426 | BOOL allowPickingGif = [self.cameraOptions sy_boolForKey:@"isGif"]; 427 | BOOL isGIF = [[TZImageManager manager] getAssetType:asset] == TZAssetModelMediaTypePhotoGif; 428 | if (!allowPickingGif && isGIF) { 429 | return NO; 430 | } 431 | return YES; 432 | } 433 | 434 | /// 异步处理获取图片 435 | - (void)handleAssets:(NSArray *)assets photos:(NSArray*)photos compressQuality:(CGFloat)compressQuality isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto completion:(void (^)(NSArray *selecteds))completion fail:(void(^)(NSError *error))fail { 436 | NSMutableArray *selectedPhotos = [NSMutableArray array]; 437 | 438 | [assets enumerateObjectsUsingBlock:^(PHAsset * _Nonnull asset, NSUInteger idx, BOOL * _Nonnull stop) { 439 | if (asset.mediaType == PHAssetMediaTypeVideo) { 440 | [[TZImageManager manager] getVideoOutputPathWithAsset:asset presetName:AVAssetExportPresetHighestQuality success:^(NSString *outputPath) { 441 | [selectedPhotos addObject:[self handleVideoData:outputPath asset:asset coverImage:photos[idx] compressQuality:compressQuality]]; 442 | if ([selectedPhotos count] == [assets count]) { 443 | completion(selectedPhotos); 444 | } 445 | if (idx + 1 == [assets count] && [selectedPhotos count] != [assets count]) { 446 | fail(nil); 447 | } 448 | } failure:^(NSString *errorMessage, NSError *error) { 449 | 450 | }]; 451 | } else { 452 | BOOL isGIF = [[TZImageManager manager] getAssetType:asset] == TZAssetModelMediaTypePhotoGif; 453 | if (isGIF || isSelectOriginalPhoto) { 454 | [[TZImageManager manager] requestImageDataForAsset:asset completion:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { 455 | [selectedPhotos addObject:[self handleOriginalPhotoData:imageData phAsset:asset isGIF:isGIF compressQuality:compressQuality]]; 456 | if ([selectedPhotos count] == [assets count]) { 457 | completion(selectedPhotos); 458 | } 459 | if (idx + 1 == [assets count] && [selectedPhotos count] != [assets count]) { 460 | fail(nil); 461 | } 462 | } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { 463 | 464 | }]; 465 | } else { 466 | [selectedPhotos addObject:[self handleCropImage:photos[idx] phAsset:asset compressQuality:compressQuality]]; 467 | if ([selectedPhotos count] == [assets count]) { 468 | completion(selectedPhotos); 469 | } 470 | } 471 | } 472 | }]; 473 | } 474 | 475 | /// 处理裁剪图片数据 476 | - (NSDictionary *)handleCropImage:(UIImage *)image phAsset:(PHAsset *)phAsset compressQuality:(CGFloat)compressQuality { 477 | [self createDir]; 478 | 479 | NSMutableDictionary *photo = [NSMutableDictionary dictionary]; 480 | NSString *filename = [NSString stringWithFormat:@"%@%@", [[NSUUID UUID] UUIDString], [phAsset valueForKey:@"filename"]]; 481 | NSString *fileExtension = [filename pathExtension]; 482 | NSMutableString *filePath = [NSMutableString string]; 483 | BOOL isPNG = [fileExtension hasSuffix:@"PNG"] || [fileExtension hasSuffix:@"png"]; 484 | 485 | if (isPNG) { 486 | [filePath appendString:[NSString stringWithFormat:@"%@ImageCropPicker/%@", NSTemporaryDirectory(), filename]]; 487 | } else { 488 | [filePath appendString:[NSString stringWithFormat:@"%@ImageCropPicker/%@.jpg", NSTemporaryDirectory(), [filename stringByDeletingPathExtension]]]; 489 | } 490 | 491 | NSData *writeData = isPNG ? UIImagePNGRepresentation(image) : UIImageJPEGRepresentation(image, compressQuality/100); 492 | [writeData writeToFile:filePath atomically:YES]; 493 | 494 | photo[@"path"] = filePath; 495 | photo[@"width"] = @(image.size.width); 496 | photo[@"height"] = @(image.size.height); 497 | NSInteger size = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil].fileSize; 498 | photo[@"size"] = @(size); 499 | photo[@"mediaType"] = @(phAsset.mediaType); 500 | if ([self.cameraOptions sy_boolForKey:@"includeBase64"]) { 501 | photo[@"data"] = [NSString stringWithFormat:@"%@", [writeData base64EncodedStringWithOptions:0]]; 502 | } 503 | 504 | return photo; 505 | } 506 | 507 | /// 处理原图数据 508 | - (NSDictionary *)handleOriginalPhotoData:(NSData *)data phAsset:(PHAsset *)phAsset isGIF:(BOOL)isGIF compressQuality:(CGFloat)compressQuality { 509 | [self createDir]; 510 | 511 | NSMutableDictionary *photo = [NSMutableDictionary dictionary]; 512 | NSString *filename = [NSString stringWithFormat:@"%@%@", [[NSUUID UUID] UUIDString], [phAsset valueForKey:@"filename"]]; 513 | NSString *fileExtension = [filename pathExtension]; 514 | UIImage *image = nil; 515 | NSData *writeData = nil; 516 | NSMutableString *filePath = [NSMutableString string]; 517 | 518 | BOOL isPNG = [fileExtension hasSuffix:@"PNG"] || [fileExtension hasSuffix:@"png"]; 519 | 520 | if (isGIF) { 521 | image = [UIImage sd_tz_animatedGIFWithData:data]; 522 | writeData = data; 523 | } else { 524 | image = [UIImage imageWithData: data]; 525 | writeData = isPNG ? UIImagePNGRepresentation(image) : UIImageJPEGRepresentation(image, compressQuality/100); 526 | } 527 | 528 | if (isPNG || isGIF) { 529 | [filePath appendString:[NSString stringWithFormat:@"%@ImageCropPicker/%@", NSTemporaryDirectory(), filename]]; 530 | } else { 531 | [filePath appendString:[NSString stringWithFormat:@"%@ImageCropPicker/%@.jpg", NSTemporaryDirectory(), [filename stringByDeletingPathExtension]]]; 532 | } 533 | 534 | [writeData writeToFile:filePath atomically:YES]; 535 | 536 | photo[@"path"] = filePath; 537 | photo[@"width"] = @(image.size.width); 538 | photo[@"height"] = @(image.size.height); 539 | NSInteger size = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil].fileSize; 540 | photo[@"size"] = @(size); 541 | photo[@"mediaType"] = @(phAsset.mediaType); 542 | if ([self.cameraOptions sy_boolForKey:@"includeBase64"] && !isGIF) { 543 | photo[@"data"] = [NSString stringWithFormat:@"data:image/jpeg;base64,%@", [writeData base64EncodedStringWithOptions:0]]; 544 | } 545 | 546 | return photo; 547 | } 548 | 549 | /// 处理视频数据 550 | - (NSDictionary *)handleVideoData:(NSString *)outputPath asset:(PHAsset *)asset coverImage:(UIImage *)coverImage compressQuality:(CGFloat)compressQuality { 551 | NSMutableDictionary *video = [NSMutableDictionary dictionary]; 552 | video[@"path"] = outputPath; 553 | video[@"fileName"] = [asset valueForKey:@"filename"]; 554 | NSInteger size = [[NSFileManager defaultManager] attributesOfItemAtPath:outputPath error:nil].fileSize; 555 | video[@"size"] = @(size); 556 | video[@"duration"] = @(asset.duration); 557 | video[@"width"] = @(asset.pixelWidth); 558 | video[@"height"] = @(asset.pixelHeight); 559 | video[@"type"] = @"video"; 560 | video[@"mime"] = @"video/mp4"; 561 | // iOS only 562 | video[@"coverUri"] = [self handleCropImage:coverImage phAsset:asset compressQuality:compressQuality][@"path"]; 563 | video[@"favorite"] = @(asset.favorite); 564 | video[@"mediaType"] = @(asset.mediaType); 565 | 566 | return video; 567 | } 568 | 569 | /// 创建SyanImageCaches缓存目录 570 | - (BOOL)createDir { 571 | NSString * path = [NSString stringWithFormat:@"%@ImageCropPicker", NSTemporaryDirectory()];; 572 | NSFileManager *fileManager = [NSFileManager defaultManager]; 573 | BOOL isDir; 574 | if (![fileManager fileExistsAtPath:path isDirectory:&isDir]) { 575 | //先判断目录是否存在,不存在才创建 576 | BOOL res = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; 577 | return res; 578 | } else return NO; 579 | } 580 | 581 | 582 | - (void)invokeSuccessWithResult:(NSArray *)photos { 583 | if (self.resolveBlock) { 584 | self.resolveBlock(photos); 585 | self.resolveBlock = nil; 586 | } 587 | } 588 | 589 | - (void)invokeError { 590 | if (self.rejectBlock) { 591 | self.rejectBlock(@"cancel", @"cancel", nil); 592 | self.rejectBlock = nil; 593 | } 594 | } 595 | 596 | + (BOOL)requiresMainQueueSetup 597 | { 598 | return YES; 599 | } 600 | 601 | - (UIViewController *)topViewController { 602 | UIViewController *rootViewController = RCTPresentedViewController(); 603 | return rootViewController; 604 | } 605 | 606 | - (dispatch_queue_t)methodQueue { 607 | return dispatch_get_main_queue(); 608 | } 609 | 610 | @end 611 | -------------------------------------------------------------------------------- /ios/ImageCropPicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 23F50D671F3C041A004B3E61 /* NSBundle+TZImagePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D4B1F3C0419004B3E61 /* NSBundle+TZImagePicker.m */; }; 11 | 23F50D681F3C041A004B3E61 /* TZAssetCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D4D1F3C0419004B3E61 /* TZAssetCell.m */; }; 12 | 23F50D691F3C041A004B3E61 /* TZAssetModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D4F1F3C0419004B3E61 /* TZAssetModel.m */; }; 13 | 23F50D6A1F3C041A004B3E61 /* TZGifPhotoPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D511F3C0419004B3E61 /* TZGifPhotoPreviewController.m */; }; 14 | 23F50D6B1F3C041A004B3E61 /* TZImageCropManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D531F3C0419004B3E61 /* TZImageCropManager.m */; }; 15 | 23F50D6C1F3C041A004B3E61 /* TZImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D551F3C041A004B3E61 /* TZImageManager.m */; }; 16 | 23F50D6D1F3C041A004B3E61 /* TZImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D581F3C041A004B3E61 /* TZImagePickerController.m */; }; 17 | 23F50D6E1F3C041A004B3E61 /* TZLocationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D5A1F3C041A004B3E61 /* TZLocationManager.m */; }; 18 | 23F50D6F1F3C041A004B3E61 /* TZPhotoPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D5C1F3C041A004B3E61 /* TZPhotoPickerController.m */; }; 19 | 23F50D701F3C041A004B3E61 /* TZPhotoPreviewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D5E1F3C041A004B3E61 /* TZPhotoPreviewCell.m */; }; 20 | 23F50D711F3C041A004B3E61 /* TZPhotoPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D601F3C041A004B3E61 /* TZPhotoPreviewController.m */; }; 21 | 23F50D721F3C041A004B3E61 /* TZProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D621F3C041A004B3E61 /* TZProgressView.m */; }; 22 | 23F50D731F3C041A004B3E61 /* TZVideoPlayerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D641F3C041A004B3E61 /* TZVideoPlayerController.m */; }; 23 | 23F50D741F3C041A004B3E61 /* UIView+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 23F50D661F3C041A004B3E61 /* UIView+Layout.m */; }; 24 | B3E7B58A1CC2AC0600A0062D /* ImageCropPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* ImageCropPicker.m */; }; 25 | EC236E871F978C3B00D528A5 /* NSDictionary+SYSafeConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = EC236E861F978C3B00D528A5 /* NSDictionary+SYSafeConvert.m */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXCopyFilesBuildPhase section */ 29 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 30 | isa = PBXCopyFilesBuildPhase; 31 | buildActionMask = 2147483647; 32 | dstPath = "include/$(PRODUCT_NAME)"; 33 | dstSubfolderSpec = 16; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 134814201AA4EA6300B7C361 /* libImageCropPicker.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libImageCropPicker.a; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 23F50D4A1F3C0419004B3E61 /* NSBundle+TZImagePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+TZImagePicker.h"; sourceTree = ""; }; 43 | 23F50D4B1F3C0419004B3E61 /* NSBundle+TZImagePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+TZImagePicker.m"; sourceTree = ""; }; 44 | 23F50D4C1F3C0419004B3E61 /* TZAssetCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZAssetCell.h; sourceTree = ""; }; 45 | 23F50D4D1F3C0419004B3E61 /* TZAssetCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZAssetCell.m; sourceTree = ""; }; 46 | 23F50D4E1F3C0419004B3E61 /* TZAssetModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZAssetModel.h; sourceTree = ""; }; 47 | 23F50D4F1F3C0419004B3E61 /* TZAssetModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZAssetModel.m; sourceTree = ""; }; 48 | 23F50D501F3C0419004B3E61 /* TZGifPhotoPreviewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZGifPhotoPreviewController.h; sourceTree = ""; }; 49 | 23F50D511F3C0419004B3E61 /* TZGifPhotoPreviewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZGifPhotoPreviewController.m; sourceTree = ""; }; 50 | 23F50D521F3C0419004B3E61 /* TZImageCropManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZImageCropManager.h; sourceTree = ""; }; 51 | 23F50D531F3C0419004B3E61 /* TZImageCropManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZImageCropManager.m; sourceTree = ""; }; 52 | 23F50D541F3C041A004B3E61 /* TZImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZImageManager.h; sourceTree = ""; }; 53 | 23F50D551F3C041A004B3E61 /* TZImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZImageManager.m; sourceTree = ""; }; 54 | 23F50D561F3C041A004B3E61 /* TZImagePickerController.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TZImagePickerController.bundle; sourceTree = ""; }; 55 | 23F50D571F3C041A004B3E61 /* TZImagePickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZImagePickerController.h; sourceTree = ""; }; 56 | 23F50D581F3C041A004B3E61 /* TZImagePickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZImagePickerController.m; sourceTree = ""; }; 57 | 23F50D591F3C041A004B3E61 /* TZLocationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZLocationManager.h; sourceTree = ""; }; 58 | 23F50D5A1F3C041A004B3E61 /* TZLocationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZLocationManager.m; sourceTree = ""; }; 59 | 23F50D5B1F3C041A004B3E61 /* TZPhotoPickerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZPhotoPickerController.h; sourceTree = ""; }; 60 | 23F50D5C1F3C041A004B3E61 /* TZPhotoPickerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZPhotoPickerController.m; sourceTree = ""; }; 61 | 23F50D5D1F3C041A004B3E61 /* TZPhotoPreviewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZPhotoPreviewCell.h; sourceTree = ""; }; 62 | 23F50D5E1F3C041A004B3E61 /* TZPhotoPreviewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZPhotoPreviewCell.m; sourceTree = ""; }; 63 | 23F50D5F1F3C041A004B3E61 /* TZPhotoPreviewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZPhotoPreviewController.h; sourceTree = ""; }; 64 | 23F50D601F3C041A004B3E61 /* TZPhotoPreviewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZPhotoPreviewController.m; sourceTree = ""; }; 65 | 23F50D611F3C041A004B3E61 /* TZProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZProgressView.h; sourceTree = ""; }; 66 | 23F50D621F3C041A004B3E61 /* TZProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZProgressView.m; sourceTree = ""; }; 67 | 23F50D631F3C041A004B3E61 /* TZVideoPlayerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZVideoPlayerController.h; sourceTree = ""; }; 68 | 23F50D641F3C041A004B3E61 /* TZVideoPlayerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZVideoPlayerController.m; sourceTree = ""; }; 69 | 23F50D651F3C041A004B3E61 /* UIView+Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Layout.h"; sourceTree = ""; }; 70 | 23F50D661F3C041A004B3E61 /* UIView+Layout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Layout.m"; sourceTree = ""; }; 71 | B3E7B5881CC2AC0600A0062D /* ImageCropPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCropPicker.h; sourceTree = ""; }; 72 | B3E7B5891CC2AC0600A0062D /* ImageCropPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCropPicker.m; sourceTree = ""; }; 73 | EC236E851F978C3B00D528A5 /* NSDictionary+SYSafeConvert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+SYSafeConvert.h"; sourceTree = ""; }; 74 | EC236E861F978C3B00D528A5 /* NSDictionary+SYSafeConvert.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+SYSafeConvert.m"; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 134814211AA4EA7D00B7C361 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 134814201AA4EA6300B7C361 /* libImageCropPicker.a */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 23F50D491F3C0419004B3E61 /* TZImagePickerController */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 23F50D4A1F3C0419004B3E61 /* NSBundle+TZImagePicker.h */, 100 | 23F50D4B1F3C0419004B3E61 /* NSBundle+TZImagePicker.m */, 101 | 23F50D4C1F3C0419004B3E61 /* TZAssetCell.h */, 102 | 23F50D4D1F3C0419004B3E61 /* TZAssetCell.m */, 103 | 23F50D4E1F3C0419004B3E61 /* TZAssetModel.h */, 104 | 23F50D4F1F3C0419004B3E61 /* TZAssetModel.m */, 105 | 23F50D501F3C0419004B3E61 /* TZGifPhotoPreviewController.h */, 106 | 23F50D511F3C0419004B3E61 /* TZGifPhotoPreviewController.m */, 107 | 23F50D521F3C0419004B3E61 /* TZImageCropManager.h */, 108 | 23F50D531F3C0419004B3E61 /* TZImageCropManager.m */, 109 | 23F50D541F3C041A004B3E61 /* TZImageManager.h */, 110 | 23F50D551F3C041A004B3E61 /* TZImageManager.m */, 111 | 23F50D561F3C041A004B3E61 /* TZImagePickerController.bundle */, 112 | 23F50D571F3C041A004B3E61 /* TZImagePickerController.h */, 113 | 23F50D581F3C041A004B3E61 /* TZImagePickerController.m */, 114 | 23F50D591F3C041A004B3E61 /* TZLocationManager.h */, 115 | 23F50D5A1F3C041A004B3E61 /* TZLocationManager.m */, 116 | 23F50D5B1F3C041A004B3E61 /* TZPhotoPickerController.h */, 117 | 23F50D5C1F3C041A004B3E61 /* TZPhotoPickerController.m */, 118 | 23F50D5D1F3C041A004B3E61 /* TZPhotoPreviewCell.h */, 119 | 23F50D5E1F3C041A004B3E61 /* TZPhotoPreviewCell.m */, 120 | 23F50D5F1F3C041A004B3E61 /* TZPhotoPreviewController.h */, 121 | 23F50D601F3C041A004B3E61 /* TZPhotoPreviewController.m */, 122 | 23F50D611F3C041A004B3E61 /* TZProgressView.h */, 123 | 23F50D621F3C041A004B3E61 /* TZProgressView.m */, 124 | 23F50D631F3C041A004B3E61 /* TZVideoPlayerController.h */, 125 | 23F50D641F3C041A004B3E61 /* TZVideoPlayerController.m */, 126 | 23F50D651F3C041A004B3E61 /* UIView+Layout.h */, 127 | 23F50D661F3C041A004B3E61 /* UIView+Layout.m */, 128 | ); 129 | path = TZImagePickerController; 130 | sourceTree = ""; 131 | }; 132 | 58B511D21A9E6C8500147676 = { 133 | isa = PBXGroup; 134 | children = ( 135 | EC236E851F978C3B00D528A5 /* NSDictionary+SYSafeConvert.h */, 136 | EC236E861F978C3B00D528A5 /* NSDictionary+SYSafeConvert.m */, 137 | 23F50D491F3C0419004B3E61 /* TZImagePickerController */, 138 | B3E7B5881CC2AC0600A0062D /* ImageCropPicker.h */, 139 | B3E7B5891CC2AC0600A0062D /* ImageCropPicker.m */, 140 | 134814211AA4EA7D00B7C361 /* Products */, 141 | ); 142 | sourceTree = ""; 143 | }; 144 | /* End PBXGroup section */ 145 | 146 | /* Begin PBXNativeTarget section */ 147 | 58B511DA1A9E6C8500147676 /* ImageCropPicker */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "ImageCropPicker" */; 150 | buildPhases = ( 151 | 58B511D71A9E6C8500147676 /* Sources */, 152 | 58B511D81A9E6C8500147676 /* Frameworks */, 153 | 58B511D91A9E6C8500147676 /* CopyFiles */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = ImageCropPicker; 160 | productName = RCTDataManager; 161 | productReference = 134814201AA4EA6300B7C361 /* libImageCropPicker.a */; 162 | productType = "com.apple.product-type.library.static"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 58B511D31A9E6C8500147676 /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | LastUpgradeCheck = 0610; 171 | ORGANIZATIONNAME = Facebook; 172 | TargetAttributes = { 173 | 58B511DA1A9E6C8500147676 = { 174 | CreatedOnToolsVersion = 6.1.1; 175 | }; 176 | }; 177 | }; 178 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ImageCropPicker" */; 179 | compatibilityVersion = "Xcode 3.2"; 180 | developmentRegion = English; 181 | hasScannedForEncodings = 0; 182 | knownRegions = ( 183 | en, 184 | ); 185 | mainGroup = 58B511D21A9E6C8500147676; 186 | productRefGroup = 58B511D21A9E6C8500147676; 187 | projectDirPath = ""; 188 | projectRoot = ""; 189 | targets = ( 190 | 58B511DA1A9E6C8500147676 /* ImageCropPicker */, 191 | ); 192 | }; 193 | /* End PBXProject section */ 194 | 195 | /* Begin PBXSourcesBuildPhase section */ 196 | 58B511D71A9E6C8500147676 /* Sources */ = { 197 | isa = PBXSourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | 23F50D6F1F3C041A004B3E61 /* TZPhotoPickerController.m in Sources */, 201 | 23F50D691F3C041A004B3E61 /* TZAssetModel.m in Sources */, 202 | 23F50D701F3C041A004B3E61 /* TZPhotoPreviewCell.m in Sources */, 203 | 23F50D6C1F3C041A004B3E61 /* TZImageManager.m in Sources */, 204 | 23F50D671F3C041A004B3E61 /* NSBundle+TZImagePicker.m in Sources */, 205 | 23F50D6A1F3C041A004B3E61 /* TZGifPhotoPreviewController.m in Sources */, 206 | EC236E871F978C3B00D528A5 /* NSDictionary+SYSafeConvert.m in Sources */, 207 | 23F50D6B1F3C041A004B3E61 /* TZImageCropManager.m in Sources */, 208 | 23F50D711F3C041A004B3E61 /* TZPhotoPreviewController.m in Sources */, 209 | 23F50D741F3C041A004B3E61 /* UIView+Layout.m in Sources */, 210 | 23F50D731F3C041A004B3E61 /* TZVideoPlayerController.m in Sources */, 211 | 23F50D721F3C041A004B3E61 /* TZProgressView.m in Sources */, 212 | 23F50D6E1F3C041A004B3E61 /* TZLocationManager.m in Sources */, 213 | B3E7B58A1CC2AC0600A0062D /* ImageCropPicker.m in Sources */, 214 | 23F50D681F3C041A004B3E61 /* TZAssetCell.m in Sources */, 215 | 23F50D6D1F3C041A004B3E61 /* TZImagePickerController.m in Sources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXSourcesBuildPhase section */ 220 | 221 | /* Begin XCBuildConfiguration section */ 222 | 58B511ED1A9E6C8500147676 /* Debug */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | COPY_PHASE_STRIP = NO; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | GCC_C_LANGUAGE_STANDARD = gnu99; 242 | GCC_DYNAMIC_NO_PIC = NO; 243 | GCC_OPTIMIZATION_LEVEL = 0; 244 | GCC_PREPROCESSOR_DEFINITIONS = ( 245 | "DEBUG=1", 246 | "$(inherited)", 247 | ); 248 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 256 | MTL_ENABLE_DEBUG_INFO = YES; 257 | ONLY_ACTIVE_ARCH = YES; 258 | SDKROOT = iphoneos; 259 | }; 260 | name = Debug; 261 | }; 262 | 58B511EE1A9E6C8500147676 /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_UNREACHABLE_CODE = YES; 278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 279 | COPY_PHASE_STRIP = YES; 280 | ENABLE_NS_ASSERTIONS = NO; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 290 | MTL_ENABLE_DEBUG_INFO = NO; 291 | SDKROOT = iphoneos; 292 | VALIDATE_PRODUCT = YES; 293 | }; 294 | name = Release; 295 | }; 296 | 58B511F01A9E6C8500147676 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | HEADER_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "\"$(SRCROOT)/../../react-native/React\"", 302 | ); 303 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 304 | OTHER_LDFLAGS = "-ObjC"; 305 | PRODUCT_NAME = ImageCropPicker; 306 | SKIP_INSTALL = YES; 307 | }; 308 | name = Debug; 309 | }; 310 | 58B511F11A9E6C8500147676 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | HEADER_SEARCH_PATHS = ( 314 | "$(inherited)", 315 | "\"$(SRCROOT)/../../react-native/React\"", 316 | ); 317 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 318 | OTHER_LDFLAGS = "-ObjC"; 319 | PRODUCT_NAME = ImageCropPicker; 320 | SKIP_INSTALL = YES; 321 | }; 322 | name = Release; 323 | }; 324 | /* End XCBuildConfiguration section */ 325 | 326 | /* Begin XCConfigurationList section */ 327 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ImageCropPicker" */ = { 328 | isa = XCConfigurationList; 329 | buildConfigurations = ( 330 | 58B511ED1A9E6C8500147676 /* Debug */, 331 | 58B511EE1A9E6C8500147676 /* Release */, 332 | ); 333 | defaultConfigurationIsVisible = 0; 334 | defaultConfigurationName = Release; 335 | }; 336 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "ImageCropPicker" */ = { 337 | isa = XCConfigurationList; 338 | buildConfigurations = ( 339 | 58B511F01A9E6C8500147676 /* Debug */, 340 | 58B511F11A9E6C8500147676 /* Release */, 341 | ); 342 | defaultConfigurationIsVisible = 0; 343 | defaultConfigurationName = Release; 344 | }; 345 | /* End XCConfigurationList section */ 346 | }; 347 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 348 | } 349 | -------------------------------------------------------------------------------- /ios/NSDictionary+SYSafeConvert.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionary+SYSafeConvert.h 3 | // ImageCropPicker 4 | // 5 | // Created by CookieJ on 2017/10/18. 6 | // Copyright © 2017年 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSMutableDictionary (SYSafeConvert) 12 | 13 | - (void)sy_setObject:(id)value forKey:(NSString *)key; 14 | 15 | - (void)sy_setInteger:(NSInteger)value forKey:(NSString *)key; 16 | 17 | - (void)sy_setBool:(BOOL)value forKey:(NSString *)key; 18 | 19 | @end 20 | 21 | @interface NSDictionary (SYSafeConvert) 22 | 23 | - (NSString *)sy_stringForKey:(NSString *)key; 24 | 25 | - (BOOL)sy_boolForKey:(NSString *)key; 26 | 27 | - (NSInteger)sy_integerForKey:(NSString *)key; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /ios/NSDictionary+SYSafeConvert.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionary+SYSafeConvert.m 3 | // ImageCropPicker 4 | // 5 | // Created by CookieJ on 2017/10/18. 6 | // Copyright © 2017年 Facebook. All rights reserved. 7 | // 8 | 9 | #import "NSDictionary+SYSafeConvert.h" 10 | 11 | @implementation NSMutableDictionary (SYSafeConvert) 12 | 13 | - (void)sy_setObject:(id)value forKey:(NSString *)key { 14 | if (![self isKindOfClass:[NSMutableDictionary class]]) { 15 | NSLog(@"类型有误,非字典无法设置值!"); 16 | return; 17 | } 18 | 19 | if (value && value != [NSNull null] && key) { 20 | [self setObject:value forKey:key]; 21 | } 22 | } 23 | 24 | - (void)sy_setBool:(BOOL)value forKey:(NSString *)key { 25 | if (![self isKindOfClass:[NSMutableDictionary class]]) { 26 | NSLog(@"类型有误,非字典无法设置值!"); 27 | return; 28 | } 29 | 30 | if (key) { 31 | [self setObject:@(value) forKey:key]; 32 | } 33 | } 34 | 35 | - (void)sy_setInteger:(NSInteger)value forKey:(NSString *)key { 36 | if (![self isKindOfClass:[NSMutableDictionary class]]) { 37 | NSLog(@"类型有误,非字典无法设置值!"); 38 | return; 39 | } 40 | 41 | if (key) { 42 | [self setObject:@(value) forKey:key]; 43 | } 44 | } 45 | 46 | @end 47 | 48 | @implementation NSDictionary (SYSafeConvert) 49 | 50 | - (BOOL)sy_boolForKey:(NSString *)key { 51 | if (![self isKindOfClass:[NSDictionary class]]) { 52 | NSLog(@"类型有误,无法从非字典取值!"); 53 | return nil; 54 | } 55 | 56 | id value = [self objectForKey:key]; 57 | if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { 58 | return [value boolValue]; 59 | } 60 | return NO; 61 | } 62 | 63 | - (NSInteger)sy_integerForKey:(NSString *)key { 64 | if (![self isKindOfClass:[NSDictionary class]]) { 65 | NSLog(@"类型有误,无法从非字典取值!"); 66 | return nil; 67 | } 68 | 69 | id value = [self objectForKey:key]; 70 | if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { 71 | return [value integerValue]; 72 | } 73 | return 0; 74 | } 75 | 76 | - (NSString *)sy_stringForKey:(NSString *)key { 77 | if (![self isKindOfClass:[NSDictionary class]]) { 78 | NSLog(@"类型有误,无法从非字典取值!"); 79 | return nil; 80 | } 81 | 82 | id value = [self objectForKey:key]; 83 | if ([value isKindOfClass:[NSString class]]) { 84 | return (NSString *)value; 85 | } 86 | if ([value isKindOfClass:[NSNumber class]]) { 87 | return [value stringValue]; 88 | } 89 | return nil; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-customized-image-picker", 3 | "version": "1.3.4", 4 | "description": "Select single or multiple images, with croping option", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/liukefu2050/react-native-customized-image-picker" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "native", 16 | "react-native", 17 | "image", 18 | "picker", 19 | "crop", 20 | "cropping", 21 | "multiple", 22 | "camera" 23 | ], 24 | "author": "Ivan Pusic,liukefu", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/liukefu2050/react-native-customized-image-picker/issues" 28 | }, 29 | "homepage": "https://github.com/liukefu2050/react-native-customized-image-picker#readme", 30 | "peerDependencies": { 31 | "react-native": ">=0.33.0" 32 | } 33 | } 34 | --------------------------------------------------------------------------------