From e1747c3d8d8c423eba896ca5466bd6cb6bea364d Mon Sep 17 00:00:00 2001 From: Bunhouth Date: Sat, 7 Mar 2020 18:39:39 +0900 Subject: [PATCH] support android version --- README.md | 15 +- android/build.gradle | 3 + .../java/com/igimagepicker/Compression.java | 135 +++++++++ .../igimagepicker/IGImagePickerModule.java | 282 ++++++++++++++++++ .../IGImagePickerPackage.java} | 7 +- .../java/com/igimagepicker/RealPathUtil.java | 224 ++++++++++++++ .../com/igimagepicker/RedBookPresenter.java | 126 ++++++++ .../com/igimagepicker/ResultCollector.java | 96 ++++++ .../com/reactlibrary/IgImagePickerModule.java | 27 -- index.android.js | 29 +- package.json | 2 +- 11 files changed, 909 insertions(+), 37 deletions(-) create mode 100644 android/src/main/java/com/igimagepicker/Compression.java create mode 100644 android/src/main/java/com/igimagepicker/IGImagePickerModule.java rename android/src/main/java/com/{reactlibrary/IgImagePickerPackage.java => igimagepicker/IGImagePickerPackage.java} (73%) create mode 100644 android/src/main/java/com/igimagepicker/RealPathUtil.java create mode 100644 android/src/main/java/com/igimagepicker/RedBookPresenter.java create mode 100644 android/src/main/java/com/igimagepicker/ResultCollector.java delete mode 100644 android/src/main/java/com/reactlibrary/IgImagePickerModule.java diff --git a/README.md b/README.md index 0059892..8cc1f5a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ const openPicker = async () => { } ``` -#### Picker options +#### Picker IOS options ```javascript const defaultOption = { compressImageMaxWidth: 780, @@ -39,3 +39,16 @@ const defaultOption = { usesFrontCamera: false }; ``` + +#### Picker Android options +```javascript +const defaultOption = { + showCamera: true, + videoSinglePick: false, + singlePickWithAutoComplete: false, + imageOnly: false, + videoOnly: false, + maxCount: 5, + columnCount: 4 +}; +``` diff --git a/android/build.gradle b/android/build.gradle index 5dd382b..ba71898 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -14,6 +14,7 @@ def DEFAULT_COMPILE_SDK_VERSION = 28 def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' def DEFAULT_MIN_SDK_VERSION = 16 def DEFAULT_TARGET_SDK_VERSION = 28 +def _glideVersion = safeExtGet("glideVersion", "4.10.0") def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback @@ -73,6 +74,8 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules + implementation 'com.ypx.yimagepicker:androidx:3.1.1' + implementation("com.github.bumptech.glide:glide:${_glideVersion}") } def configureReactNativePom(def pom) { diff --git a/android/src/main/java/com/igimagepicker/Compression.java b/android/src/main/java/com/igimagepicker/Compression.java new file mode 100644 index 0000000..dffa5c9 --- /dev/null +++ b/android/src/main/java/com/igimagepicker/Compression.java @@ -0,0 +1,135 @@ +//Checkout here https://github.com/ivpusic/react-native-image-crop-picker/blob/master/android/src/main/java/com/reactnative/ivpusic/imagepicker/Compression.java +package com.igimagepicker; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.os.Environment; +import android.util.Log; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +/** + * Created by ipusic on 12/27/16. + */ + +class Compression { + + File resize(String originalImagePath, int maxWidth, int maxHeight, int quality) throws IOException { + Bitmap original = BitmapFactory.decodeFile(originalImagePath); + + int width = original.getWidth(); + int height = original.getHeight(); + + // Use original image exif orientation data to preserve image orientation for the resized bitmap + ExifInterface originalExif = new ExifInterface(originalImagePath); + int originalOrientation = originalExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); + + Matrix rotationMatrix = new Matrix(); + int rotationAngleInDegrees = getRotationInDegreesForOrientationTag(originalOrientation); + rotationMatrix.postRotate(rotationAngleInDegrees); + + float ratioBitmap = (float) width / (float) height; + float ratioMax = (float) maxWidth / (float) maxHeight; + + int finalWidth = maxWidth; + int finalHeight = maxHeight; + + if (ratioMax > 1) { + finalWidth = (int) ((float) maxHeight * ratioBitmap); + } else { + finalHeight = (int) ((float) maxWidth / ratioBitmap); + } + + Bitmap resized = Bitmap.createScaledBitmap(original, finalWidth, finalHeight, true); + resized = Bitmap.createBitmap(resized, 0, 0, finalWidth, finalHeight, rotationMatrix, true); + + File imageDirectory = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + + if(!imageDirectory.exists()) { + Log.d("ig-image-picker", "Pictures Directory is not existing. Will create this directory."); + imageDirectory.mkdirs(); + } + + File resizeImageFile = new File(imageDirectory, UUID.randomUUID() + ".jpg"); + + OutputStream os = new BufferedOutputStream(new FileOutputStream(resizeImageFile)); + resized.compress(Bitmap.CompressFormat.JPEG, quality, os); + + os.close(); + original.recycle(); + resized.recycle(); + + return resizeImageFile; + } + + int getRotationInDegreesForOrientationTag(int orientationTag) { + switch(orientationTag){ + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_270: + return -90; + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + default: + return 0; + } + } + + File compressImage(final ReadableMap options, final String originalImagePath, final BitmapFactory.Options bitmapOptions) throws IOException { + Integer maxWidth = options.hasKey("compressImageMaxWidth") ? options.getInt("compressImageMaxWidth") : null; + Integer maxHeight = options.hasKey("compressImageMaxHeight") ? options.getInt("compressImageMaxHeight") : null; + Double quality = options.hasKey("compressImageQuality") ? options.getDouble("compressImageQuality") : null; + + boolean isLossLess = (quality == null || quality == 1.0); + boolean useOriginalWidth = (maxWidth == null || maxWidth >= bitmapOptions.outWidth); + boolean useOriginalHeight = (maxHeight == null || maxHeight >= bitmapOptions.outHeight); + + List knownMimes = Arrays.asList("image/jpeg", "image/jpg", "image/png", "image/gif", "image/tiff"); + boolean isKnownMimeType = (bitmapOptions.outMimeType != null && knownMimes.contains(bitmapOptions.outMimeType.toLowerCase())); + + if (isLossLess && useOriginalWidth && useOriginalHeight && isKnownMimeType) { + Log.d("ig-image-picker", "Skipping image compression"); + return new File(originalImagePath); + } + + Log.d("ig-image-picker", "Image compression activated"); + + // compression quality + int targetQuality = quality != null ? (int) (quality * 100) : 100; + Log.d("ig-image-picker", "Compressing image with quality " + targetQuality); + + if (maxWidth == null) { + maxWidth = bitmapOptions.outWidth; + } else { + maxWidth = Math.min(maxWidth, bitmapOptions.outWidth); + } + + if (maxHeight == null) { + maxHeight = bitmapOptions.outHeight; + } else { + maxHeight = Math.min(maxHeight, bitmapOptions.outHeight); + } + + return resize(originalImagePath, maxWidth, maxHeight, targetQuality); + } + + synchronized void compressVideo(final Activity activity, final ReadableMap options, final String originalVideo, final String compressedVideo, final Promise promise) { + // todo: video compression + // failed attempt 1: ffmpeg => slow and licensing issues + promise.resolve(originalVideo); + } +} diff --git a/android/src/main/java/com/igimagepicker/IGImagePickerModule.java b/android/src/main/java/com/igimagepicker/IGImagePickerModule.java new file mode 100644 index 0000000..3bcd335 --- /dev/null +++ b/android/src/main/java/com/igimagepicker/IGImagePickerModule.java @@ -0,0 +1,282 @@ +package com.igimagepicker; + +import com.facebook.react.bridge.PromiseImpl; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.Callback; + + +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; +import com.ypx.imagepicker.ImagePicker; +import com.ypx.imagepicker.bean.ImageItem; +import com.ypx.imagepicker.bean.MimeType; +import com.ypx.imagepicker.bean.PickerError; +import com.ypx.imagepicker.data.OnImagePickCompleteListener2; +import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import android.app.Activity; +import android.content.ContentResolver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Build; +import android.webkit.MimeTypeMap; + +import java.io.File; +import java.util.UUID; + +public class IGImagePickerModule extends ReactContextBaseJavaModule { + + private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"; + private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND"; + private final ReactApplicationContext reactContext; + private ResultCollector resultCollector = new ResultCollector(); + private RedBookPresenter redBookPresenter; + private Compression compression = new Compression(); + private ReadableMap options; + + public IGImagePickerModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + this.redBookPresenter = new RedBookPresenter(); + } + + + @Override + public String getName() { + return "IGImagePicker"; + } + + private String getTmpDir(Activity activity) { + String tmpDir = activity.getCacheDir() + "/react-native-ig-image-picker"; + new File(tmpDir).mkdir(); + + return tmpDir; + } + + private BitmapFactory.Options validateImage(String path) throws Exception { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inPreferredConfig = Bitmap.Config.RGB_565; + options.inDither = true; + + BitmapFactory.decodeFile(path, options); + + if (options.outMimeType == null || options.outWidth == 0 || options.outHeight == 0) { + throw new Exception("Invalid image selected"); + } + + return options; + } + + private void setConfiguration(final ReadableMap options) { + this.options = options; + } + + private void redBookPick(final ReadableMap options) { + boolean isShowCamera = options.hasKey("showCamera") && options.getBoolean("showCamera") || true; + boolean isVideoSinglePick = options.hasKey("videoSinglePick") && options.getBoolean("videoSinglePick") || false; + boolean isSinglePickWithAutoComplete = options.hasKey("singlePickWithAutoComplete") && options.getBoolean("singlePickWithAutoComplete") || false; + boolean isImageOnly = options.hasKey("imageOnly") && options.getBoolean("imageOnly") || false; + boolean isVideoOnly = options.hasKey("videoOnly") && options.hasKey("videoOnly") || false; + int maxCount = options.hasKey("maxCount") ? options.getInt("maxCount") : 5; + int columnCount = options.hasKey("columnCount") ? options.getInt("columnCount") : 4; + Set mimeTypes = new HashSet<>(); + if(!isVideoOnly && !isImageOnly) { + mimeTypes.add(MimeType.JPEG); + mimeTypes.add(MimeType.PNG); + mimeTypes.add(MimeType.MP4); + mimeTypes.add(MimeType.MKV); + } + + if(isImageOnly) { + mimeTypes.add(MimeType.JPEG); + mimeTypes.add(MimeType.PNG); + } + + if(isVideoOnly) { + mimeTypes.add(MimeType.MP4); + mimeTypes.add(MimeType.MKV); + } + + runOnUiThread(() -> { + if (getCurrentActivity() != null) { + ImagePicker.withCrop(redBookPresenter) + .setMaxCount(maxCount) + .showCamera(isShowCamera) + .setSinglePickWithAutoComplete(isSinglePickWithAutoComplete) + .assignGapState(false) + .setColumnCount(columnCount) + .mimeTypes(mimeTypes) + .setVideoSinglePick(isVideoSinglePick) + .setMaxVideoDuration(120000L) + .setMinVideoDuration(5000L) + .pick(getCurrentActivity(), new OnImagePickCompleteListener2() { + @Override + public void onPickFailed(PickerError error) { + resultCollector.notifyProblem(String.valueOf(error.getCode()), error.getMessage()); + } + + @Override + public void onImagePickComplete(ArrayList items) { + + resultCollector.setWaitCount(items.size()); + for (int i = 0; i < items.size(); i++) { + ImageItem image = items.get(i); + String uri = image.getCropUrl(); + if(image.getCropUrl() == null) { + uri = image.getPath(); + } + try { + getAsyncSelection(getCurrentActivity(), uri, false); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }); + } + }); + + } + + + private WritableMap getImage(String path) throws Exception { + WritableMap image = new WritableNativeMap(); + + if (path.startsWith("http://") || path.startsWith("https://")) { + throw new Exception("Cannot select remote files"); + } + BitmapFactory.Options original = validateImage(path); + + // if compression options are provided image will be compressed. If none options is provided, + // then original image will be returned +// File compressedImage = compression.compressImage(options, path, original); +// String compressedImagePath = compressedImage.getPath(); + BitmapFactory.Options options = validateImage(path); + long modificationDate = new File(path).lastModified(); + + image.putString("path", "file://" + path); + image.putInt("width", options.outWidth); + image.putInt("height", options.outHeight); + image.putString("mime", options.outMimeType); + image.putInt("size", (int) new File(path).length()); + image.putString("modificationDate", String.valueOf(modificationDate)); + + return image; + } + + + private void getAsyncSelection(final Activity activity, String uri, boolean isCamera) throws Exception { + String path = uri; + if (path == null || path.isEmpty()) { + resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, "Cannot resolve asset path."); + return; + } + + String mime = getMimeType(path); + if (mime != null && mime.startsWith("video/")) { + getVideo(activity, path, mime); + return; + } + + resultCollector.notifySuccess(getImage(path)); + } + + private String resolveRealPath(Activity activity, Uri uri, boolean isCamera) throws IOException { + String path; + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + path = RealPathUtil.getRealPathFromURI(activity, uri); + } else { + + path = RealPathUtil.getRealPathFromURI(activity, uri); + } + + return path; + } + + private Bitmap validateVideo(String path) throws Exception { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(path); + Bitmap bmp = retriever.getFrameAtTime(); + + if (bmp == null) { + throw new Exception("Cannot retrieve video data"); + } + + return bmp; + } + + private void getVideo(final Activity activity, final String path, final String mime) throws Exception { + validateVideo(path); + final String compressedVideoPath = getTmpDir(activity) + "/" + UUID.randomUUID().toString() + ".mp4"; + + new Thread(() -> compression.compressVideo(activity, options, path, compressedVideoPath, new PromiseImpl(new Callback() { + @Override + public void invoke(Object... args) { + String videoPath = (String) args[0]; + + try { + Bitmap bmp = validateVideo(videoPath); + long modificationDate = new File(videoPath).lastModified(); + + WritableMap video = new WritableNativeMap(); + video.putInt("width", bmp.getWidth()); + video.putInt("height", bmp.getHeight()); + video.putString("mime", mime); + video.putInt("size", (int) new File(videoPath).length()); + video.putString("path", "file://" + videoPath); + video.putString("modificationDate", String.valueOf(modificationDate)); + + resultCollector.notifySuccess(video); + } catch (Exception e) { + resultCollector.notifyProblem(E_NO_IMAGE_DATA_FOUND, e); + } + } + }, args -> { + WritableNativeMap ex = (WritableNativeMap) args[0]; + resultCollector.notifyProblem(ex.getString("code"), ex.getString("message")); + }))).run(); + } + + private String getMimeType(String url) { + String mimeType = null; + Uri uri = Uri.fromFile(new File(url)); + if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + ContentResolver cr = this.reactContext.getContentResolver(); + mimeType = cr.getType(uri); + } else { + String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri + .toString()); + if (fileExtension != null) { + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase()); + } + } + return mimeType; + } + + + @ReactMethod + public void openPicker(final ReadableMap options, final Promise promise) { + final Activity activity = getCurrentActivity(); + + if (activity == null) { + promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist"); + return; + } + resultCollector.setup(promise, true); + setConfiguration(options); + redBookPick(options); + } +} diff --git a/android/src/main/java/com/reactlibrary/IgImagePickerPackage.java b/android/src/main/java/com/igimagepicker/IGImagePickerPackage.java similarity index 73% rename from android/src/main/java/com/reactlibrary/IgImagePickerPackage.java rename to android/src/main/java/com/igimagepicker/IGImagePickerPackage.java index dcf4121..9b30ddc 100644 --- a/android/src/main/java/com/reactlibrary/IgImagePickerPackage.java +++ b/android/src/main/java/com/igimagepicker/IGImagePickerPackage.java @@ -1,4 +1,4 @@ -package com.reactlibrary; +package com.igimagepicker; import java.util.Arrays; import java.util.Collections; @@ -8,12 +8,11 @@ import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; -public class IgImagePickerPackage implements ReactPackage { +public class IGImagePickerPackage implements ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new IgImagePickerModule(reactContext)); + return Arrays.asList(new IGImagePickerModule(reactContext)); } @Override diff --git a/android/src/main/java/com/igimagepicker/RealPathUtil.java b/android/src/main/java/com/igimagepicker/RealPathUtil.java new file mode 100644 index 0000000..d7e329f --- /dev/null +++ b/android/src/main/java/com/igimagepicker/RealPathUtil.java @@ -0,0 +1,224 @@ +// Checkout here https://github.com/ivpusic/react-native-image-crop-picker/blob/master/android/src/main/java/com/reactnative/ivpusic/imagepicker/RealPathUtil.java + +package com.igimagepicker; + +import android.annotation.TargetApi; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +class RealPathUtil { + @TargetApi(Build.VERSION_CODES.KITKAT) + static String getRealPathFromURI(final Context context, final Uri uri) throws IOException { + + final boolean isKitKat = Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else { + final int splitIndex = docId.indexOf(':', 1); + final String tag = docId.substring(0, splitIndex); + final String path = docId.substring(splitIndex + 1); + + String nonPrimaryVolume = getPathToNonPrimaryVolume(context, tag); + if (nonPrimaryVolume != null) { + String result = nonPrimaryVolume + "/" + path; + File file = new File(result); + if (file.exists() && file.canRead()) { + return result; + } + return null; + } + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[] { + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * If an image/video has been selected from a cloud storage, this method + * should be call to download the file in the cache folder. + * + * @param context The context + * @param fileName donwloaded file's name + * @param uri file's URI + * @return file that has been written + */ + private static File writeToFile(Context context, String fileName, Uri uri) { + String tmpDir = context.getCacheDir() + "/react-native-image-crop-picker"; + Boolean created = new File(tmpDir).mkdir(); + fileName = fileName.substring(fileName.lastIndexOf('/') + 1); + File path = new File(tmpDir); + File file = new File(path, fileName); + try { + FileOutputStream oos = new FileOutputStream(file); + byte[] buf = new byte[8192]; + InputStream is = context.getContentResolver().openInputStream(uri); + int c = 0; + while ((c = is.read(buf, 0, buf.length)) > 0) { + oos.write(buf, 0, c); + oos.flush(); + } + oos.close(); + is.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return file; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + private static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String[] projection = { + MediaStore.MediaColumns.DATA, + MediaStore.MediaColumns.DISPLAY_NAME, + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + // Fall back to writing to file if _data column does not exist + final int index = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); + String path = index > -1 ? cursor.getString(index) : null; + if (path != null) { + return cursor.getString(index); + } else { + final int indexDisplayName = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); + String fileName = cursor.getString(indexDisplayName); + File fileWritten = writeToFile(context, fileName, uri); + return fileWritten.getAbsolutePath(); + } + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + private static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static String getPathToNonPrimaryVolume(Context context, String tag) { + File[] volumes = context.getExternalCacheDirs(); + if (volumes != null) { + for (File volume : volumes) { + if (volume != null) { + String path = volume.getAbsolutePath(); + if (path != null) { + int index = path.indexOf(tag); + if (index != -1) { + return path.substring(0, index) + tag; + } + } + } + } + } + return null; + } + +} diff --git a/android/src/main/java/com/igimagepicker/RedBookPresenter.java b/android/src/main/java/com/igimagepicker/RedBookPresenter.java new file mode 100644 index 0000000..8b228fb --- /dev/null +++ b/android/src/main/java/com/igimagepicker/RedBookPresenter.java @@ -0,0 +1,126 @@ +package com.igimagepicker; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Color; +import androidx.annotation.Nullable; +import android.view.View; +import android.widget.ImageView; +import android.widget.Toast; +import com.jp.bene.app.R; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DecodeFormat; +import com.bumptech.glide.request.RequestOptions; +import com.ypx.imagepicker.adapter.PickerItemAdapter; +import com.ypx.imagepicker.bean.ImageItem; +import com.ypx.imagepicker.bean.selectconfig.BaseSelectConfig; +import com.ypx.imagepicker.data.ICameraExecutor; +import com.ypx.imagepicker.data.IReloadExecutor; +import com.ypx.imagepicker.data.ProgressSceneEnum; +import com.ypx.imagepicker.presenter.IPickerPresenter; +import com.ypx.imagepicker.utils.PViewSizeUtils; +import com.ypx.imagepicker.views.PickerUiConfig; +import com.ypx.imagepicker.views.redbook.RedBookUiProvider; + +import java.util.ArrayList; + + +/** + * 小红书剪裁样式Presenter实现类 + */ +public class RedBookPresenter implements IPickerPresenter { + + @Override + public void displayImage(View view, ImageItem item, int size, boolean isThumbnail) { + if (item.getUri() != null) { + Glide.with(view.getContext()).load(item.getUri()).apply(new RequestOptions() + .format(isThumbnail ? DecodeFormat.PREFER_RGB_565 : DecodeFormat.PREFER_ARGB_8888)) + .into((ImageView) view); + } else { + Glide.with(view.getContext()).load(item.path).apply(new RequestOptions() + .format(isThumbnail ? DecodeFormat.PREFER_RGB_565 : DecodeFormat.PREFER_ARGB_8888)) + .into((ImageView) view); + } + } + /** + * @param context 上下文 + * @return PickerUiConfig UI配置类 + */ + @Override + public PickerUiConfig getUiConfig(Context context) { + PickerUiConfig uiConfig = new PickerUiConfig(); + uiConfig.setThemeColor(Color.RED); + uiConfig.setShowStatusBar(false); + uiConfig.setStatusBarColor(Color.BLACK); + uiConfig.setPickerBackgroundColor(Color.BLACK); + uiConfig.setFolderListOpenDirection(PickerUiConfig.DIRECTION_TOP); + uiConfig.setFolderListOpenMaxMargin(PViewSizeUtils.dp(context, 200)); + + uiConfig.setPickerUiProvider(new RedBookUiProvider()); + return uiConfig; + } + + @Override + public void tip(Context context, String msg) { + if (context == null) { + return; + } + Toast.makeText(context.getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); + } + + /** + * 选择超过数量限制提示 + * + * @param context 上下文 + * @param maxCount 最大数量 + */ + @Override + public void overMaxCountTip(Context context, int maxCount) { + if (context == null) { + return; + } + } + + @Override + public DialogInterface showProgressDialog(@Nullable Activity activity, ProgressSceneEnum progressSceneEnum) { + return ProgressDialog.show(activity, null, String.valueOf(R.string.picker_loading)); + } + + @Override + public boolean interceptPickerCompleteClick(final Activity activity, final ArrayList selectedList, + BaseSelectConfig selectConfig) { + return false; + } + + /** + * 拦截选择器取消操作,用于弹出二次确认框 + * + * @param activity 当前选择器页面 + * @param selectedList 当前已经选择的文件列表 + * @return true:则拦截选择器取消, false,不处理选择器取消操作 + */ + @Override + public boolean interceptPickerCancel(final Activity activity, ArrayList selectedList) { + if (activity == null || activity.isFinishing() || activity.isDestroyed()) { + return false; + } + return false; + } + + @Override + public boolean interceptItemClick(@Nullable Activity activity, ImageItem imageItem, + ArrayList selectImageList, + ArrayList allSetImageList, + BaseSelectConfig selectConfig, + PickerItemAdapter adapter, + @Nullable IReloadExecutor reloadExecutor) { + return false; + } + + @Override + public boolean interceptCameraClick(@Nullable Activity activity, ICameraExecutor takePhoto) { + return false; + } +} diff --git a/android/src/main/java/com/igimagepicker/ResultCollector.java b/android/src/main/java/com/igimagepicker/ResultCollector.java new file mode 100644 index 0000000..5c09f9b --- /dev/null +++ b/android/src/main/java/com/igimagepicker/ResultCollector.java @@ -0,0 +1,96 @@ +/** + * Created by ipusic on 12/28/16. + * https://github.com/ivpusic/react-native-ig-image-picker/blob/master/android/src/main/java/com/reactnative/ivpusic/imagepicker/ResultCollector.java + */ + +package com.igimagepicker; + +import android.util.Log; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeArray; +import java.util.concurrent.atomic.AtomicInteger; + + +class ResultCollector { + private Promise promise; + private int waitCount; + private boolean multiple; + private AtomicInteger waitCounter; + private WritableArray arrayResult; + private boolean resultSent; + + synchronized void setup(Promise promise, boolean multiple) { + this.promise = promise; + this.multiple = multiple; + + this.resultSent = false; + this.waitCount = 0; + this.waitCounter = new AtomicInteger(0); + + if (multiple) { + this.arrayResult = new WritableNativeArray(); + } + } + + // if user has provided "multiple" option, we will wait for X number of result to come, + // and also return result as an array + synchronized void setWaitCount(int waitCount) { + this.waitCount = waitCount; + this.waitCounter = new AtomicInteger(0); + } + + synchronized private boolean isRequestValid() { + if (resultSent) { + Log.w("ig-image-picker", "Skipping result, already sent..."); + return false; + } + + if (promise == null) { + Log.w("ig-image-picker", "Trying to notify success but promise is not set"); + return false; + } + + return true; + } + + synchronized void notifySuccess(WritableMap result) { + if (!isRequestValid()) { + return; + } + + if (multiple) { + arrayResult.pushMap(result); + int currentCount = waitCounter.addAndGet(1); + + if (currentCount == waitCount) { + promise.resolve(arrayResult); + resultSent = true; + } + } else { + promise.resolve(result); + resultSent = true; + } + } + + synchronized void notifyProblem(String code, String message) { + if (!isRequestValid()) { + return; + } + + Log.e("ig-image-picker", "Promise rejected. " + message); + promise.reject(code, message); + resultSent = true; + } + + synchronized void notifyProblem(String code, Throwable throwable) { + if (!isRequestValid()) { + return; + } + + Log.e("ig-image-picker", "Promise rejected. " + throwable.getMessage()); + promise.reject(code, throwable); + resultSent = true; + } +} diff --git a/android/src/main/java/com/reactlibrary/IgImagePickerModule.java b/android/src/main/java/com/reactlibrary/IgImagePickerModule.java deleted file mode 100644 index 3784661..0000000 --- a/android/src/main/java/com/reactlibrary/IgImagePickerModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.reactlibrary; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.Callback; - -public class IgImagePickerModule extends ReactContextBaseJavaModule { - - private final ReactApplicationContext reactContext; - - public IgImagePickerModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - - @Override - public String getName() { - return "IgImagePicker"; - } - - @ReactMethod - public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { - // TODO: Implement some actually useful functionality - callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); - } -} diff --git a/index.android.js b/index.android.js index 83fe2cf..1dc93f2 100644 --- a/index.android.js +++ b/index.android.js @@ -1,13 +1,34 @@ -export const showImagePicker = options => { - console.log("NOT SUPPORT"); +import {NativeModules} from "react-native"; +const {IGImagePicker} = NativeModules; + +const defaultOption = { + showCamera: true, + videoSinglePick: false, + singlePickWithAutoComplete: false, + imageOnly: false, + videoOnly: false, + maxCount: 5, + columnCount: 4 +}; + +export const showImagePicker = (options = {}) => { + return IGImagePicker.openPicker({...defaultOption, ...options}); }; export const libaryPicker = options => { - console.log("NOT SUPPORT"); + return IGImagePicker.openPicker({ + ...defaultOption, + ...options, + imageOnly: true + }); }; export const videoPicker = options => { - console.log("NOT SUPPORT"); + return IGImagePicker.openPicker({ + ...defaultOption, + ...options, + videoOnly: true + }); }; export default { diff --git a/package.json b/package.json index dd3b922..2c08d0e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-ig-image-picker", "title": "React Native Image Picker Like Instragram", - "version": "1.0.2", + "version": "1.1.0", "description": "Instagram-like image picker & filters for iOS", "main": "index.js", "scripts": {