From 4e629c4476dd75cdba42ca917578276a2a5d531c Mon Sep 17 00:00:00 2001 From: Ian Keith Date: Wed, 23 Sep 2020 16:43:20 -0400 Subject: [PATCH] chore: Remove camera plugin (#3611) --- .../main/java/com/getcapacitor/Bridge.java | 2 - .../getcapacitor/BridgeWebChromeClient.java | 27 +- .../java/com/getcapacitor/plugin/Camera.java | 565 ------------------ .../plugin/camera/CameraResultType.java | 17 - .../plugin/camera/CameraSettings.java | 98 --- .../plugin/camera/CameraSource.java | 17 - .../plugin/camera/CameraUtils.java | 34 -- .../plugin/camera/ExifWrapper.java | 153 ----- .../plugin/camera/ImageUtils.java | 175 ------ core/src/core-plugin-definitions.ts | 145 ----- core/src/web-plugins.ts | 3 - ios/Capacitor/Capacitor/Plugins/Camera.swift | 423 ------------- .../Capacitor/Plugins/DefaultPlugins.m | 4 - 13 files changed, 25 insertions(+), 1638 deletions(-) delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraResultType.java delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSource.java delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraUtils.java delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ExifWrapper.java delete mode 100644 android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java delete mode 100644 ios/Capacitor/Capacitor/Plugins/Camera.swift diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index 2156dd3f06..23e4183077 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -16,7 +16,6 @@ import android.webkit.WebSettings; import android.webkit.WebView; import com.getcapacitor.plugin.App; -import com.getcapacitor.plugin.Camera; import com.getcapacitor.plugin.Geolocation; import com.getcapacitor.plugin.Keyboard; import com.getcapacitor.plugin.LocalNotifications; @@ -379,7 +378,6 @@ private void initWebView() { private void registerAllPlugins() { this.registerPlugin(App.class); this.registerPlugin(BackgroundTask.class); - this.registerPlugin(Camera.class); this.registerPlugin(LocalNotifications.class); this.registerPlugin(Geolocation.class); this.registerPlugin(Keyboard.class); diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java index 393f263be0..3cfab02742 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Environment; import android.provider.MediaStore; import android.view.View; import android.webkit.ConsoleMessage; @@ -17,9 +18,14 @@ import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebView; -import com.getcapacitor.plugin.camera.CameraUtils; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.FileProvider; +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.List; import org.apache.cordova.CordovaPlugin; import org.json.JSONException; @@ -284,7 +290,7 @@ private boolean showImageCapturePicker(final ValueCallback filePathCallba final Uri imageFileUri; try { - imageFileUri = CameraUtils.createImageFileUri(bridge.getActivity(), bridge.getContext().getPackageName()); + imageFileUri = createImageFileUri(); } catch (Exception ex) { Logger.error("Unable to create temporary media capture file: " + ex.getMessage()); return false; @@ -421,4 +427,21 @@ public boolean isValidMsg(String msg) { msg.equalsIgnoreCase("console.groupEnd") ); } + + private Uri createImageFileUri() throws IOException { + Activity activity = bridge.getActivity(); + File photoFile = createImageFile(activity); + return FileProvider.getUriForFile(activity, bridge.getContext().getPackageName() + ".fileprovider", photoFile); + } + + private File createImageFile(Activity activity) throws IOException { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "JPEG_" + timeStamp + "_"; + File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES); + + File image = File.createTempFile(imageFileName, ".jpg", storageDir); + + return image; + } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java deleted file mode 100644 index 4d6abc3b2f..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/Camera.java +++ /dev/null @@ -1,565 +0,0 @@ -package com.getcapacitor.plugin; - -import android.Manifest; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Bundle; -import android.provider.MediaStore; -import android.util.Base64; -import androidx.core.content.FileProvider; -import com.getcapacitor.Dialogs; -import com.getcapacitor.FileUtils; -import com.getcapacitor.JSObject; -import com.getcapacitor.Logger; -import com.getcapacitor.NativePlugin; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.PluginRequestCodes; -import com.getcapacitor.plugin.camera.CameraResultType; -import com.getcapacitor.plugin.camera.CameraSettings; -import com.getcapacitor.plugin.camera.CameraSource; -import com.getcapacitor.plugin.camera.CameraUtils; -import com.getcapacitor.plugin.camera.ExifWrapper; -import com.getcapacitor.plugin.camera.ImageUtils; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * The Camera plugin makes it easy to take a photo or have the user select a photo - * from their albums. - * - * On Android, this plugin sends an intent that opens the stock Camera app. - * - * Adapted from https://developer.android.com/training/camera/photobasics.html - */ -@NativePlugin(requestCodes = { Camera.REQUEST_IMAGE_CAPTURE, Camera.REQUEST_IMAGE_PICK, Camera.REQUEST_IMAGE_EDIT }) -public class Camera extends Plugin { - // Request codes - static final int REQUEST_IMAGE_CAPTURE = PluginRequestCodes.CAMERA_IMAGE_CAPTURE; - static final int REQUEST_IMAGE_PICK = PluginRequestCodes.CAMERA_IMAGE_PICK; - static final int REQUEST_IMAGE_EDIT = PluginRequestCodes.CAMERA_IMAGE_EDIT; - // Message constants - private static final String INVALID_RESULT_TYPE_ERROR = "Invalid resultType option"; - private static final String PERMISSION_DENIED_ERROR = "Unable to access camera, user denied permission request"; - private static final String NO_CAMERA_ERROR = "Device doesn't have a camera available"; - private static final String NO_CAMERA_ACTIVITY_ERROR = "Unable to resolve camera activity"; - private static final String IMAGE_FILE_SAVE_ERROR = "Unable to create photo on disk"; - private static final String IMAGE_PROCESS_NO_FILE_ERROR = "Unable to process image, file not found on disk"; - private static final String UNABLE_TO_PROCESS_IMAGE = "Unable to process image"; - private static final String IMAGE_EDIT_ERROR = "Unable to edit image"; - private static final String IMAGE_GALLERY_SAVE_ERROR = "Unable to save the image in the gallery"; - - private String imageFileSavePath; - private String imageEditedFileSavePath; - private Uri imageFileUri; - private boolean isEdited = false; - - private CameraSettings settings = new CameraSettings(); - - @PluginMethod - public void getPhoto(PluginCall call) { - isEdited = false; - - saveCall(call); - - settings = getSettings(call); - - doShow(call); - } - - private void doShow(PluginCall call) { - switch (settings.getSource()) { - case PROMPT: - showPrompt(call); - break; - case CAMERA: - showCamera(call); - break; - case PHOTOS: - showPhotos(call); - break; - default: - showPrompt(call); - break; - } - } - - private void showPrompt(final PluginCall call) { - // We have all necessary permissions, open the camera - String promptLabelPhoto = call.getString("promptLabelPhoto", "From Photos"); - String promptLabelPicture = call.getString("promptLabelPicture", "Take Picture"); - - JSObject fromPhotos = new JSObject(); - fromPhotos.put("title", promptLabelPhoto); - JSObject takePicture = new JSObject(); - takePicture.put("title", promptLabelPicture); - Object[] options = new Object[] { fromPhotos, takePicture }; - - Dialogs.actions( - getActivity(), - options, - index -> { - if (index == 0) { - settings.setSource(CameraSource.PHOTOS); - openPhotos(call); - } else if (index == 1) { - settings.setSource(CameraSource.CAMERA); - openCamera(call); - } - }, - () -> call.error("User cancelled photos app") - ); - } - - private void showCamera(final PluginCall call) { - if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { - call.error(NO_CAMERA_ERROR); - return; - } - openCamera(call); - } - - private void showPhotos(final PluginCall call) { - openPhotos(call); - } - - private boolean checkCameraPermissions(PluginCall call) { - // If we want to save to the gallery, we need two permissions - if ( - settings.isSaveToGallery() && - !(hasPermission(Manifest.permission.CAMERA) && hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) - ) { - pluginRequestPermissions( - new String[] { - Manifest.permission.CAMERA, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_EXTERNAL_STORAGE - }, - REQUEST_IMAGE_CAPTURE - ); - return false; - } - // If we don't need to save to the gallery, we can just ask for camera permissions - else if (!hasPermission(Manifest.permission.CAMERA)) { - pluginRequestPermission(Manifest.permission.CAMERA, REQUEST_IMAGE_CAPTURE); - return false; - } - return true; - } - - private boolean checkPhotosPermissions(PluginCall call) { - if (!hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { - pluginRequestPermission(Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_IMAGE_CAPTURE); - return false; - } - return true; - } - - private CameraSettings getSettings(PluginCall call) { - CameraSettings settings = new CameraSettings(); - settings.setResultType(getResultType(call.getString("resultType"))); - settings.setSaveToGallery(call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY)); - settings.setAllowEditing(call.getBoolean("allowEditing", false)); - settings.setQuality(call.getInt("quality", CameraSettings.DEFAULT_QUALITY)); - settings.setWidth(call.getInt("width", 0)); - settings.setHeight(call.getInt("height", 0)); - settings.setShouldResize(settings.getWidth() > 0 || settings.getHeight() > 0); - settings.setShouldCorrectOrientation(call.getBoolean("correctOrientation", CameraSettings.DEFAULT_CORRECT_ORIENTATION)); - settings.setPreserveAspectRatio(call.getBoolean("preserveAspectRatio", false)); - try { - settings.setSource(CameraSource.valueOf(call.getString("source", CameraSource.PROMPT.getSource()))); - } catch (IllegalArgumentException ex) { - settings.setSource(CameraSource.PROMPT); - } - return settings; - } - - private CameraResultType getResultType(String resultType) { - if (resultType == null) { - return null; - } - try { - return CameraResultType.valueOf(resultType.toUpperCase()); - } catch (IllegalArgumentException ex) { - Logger.debug(getLogTag(), "Invalid result type \"" + resultType + "\", defaulting to base64"); - return CameraResultType.BASE64; - } - } - - public void openCamera(final PluginCall call) { - if (checkCameraPermissions(call)) { - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { - // If we will be saving the photo, send the target file along - try { - String appId = getAppId(); - File photoFile = CameraUtils.createImageFile(getActivity()); - imageFileSavePath = photoFile.getAbsolutePath(); - // TODO: Verify provider config exists - imageFileUri = FileProvider.getUriForFile(getActivity(), appId + ".fileprovider", photoFile); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri); - } catch (Exception ex) { - call.error(IMAGE_FILE_SAVE_ERROR, ex); - return; - } - - startActivityForResult(call, takePictureIntent, REQUEST_IMAGE_CAPTURE); - } else { - call.error(NO_CAMERA_ACTIVITY_ERROR); - } - } - } - - public void openPhotos(final PluginCall call) { - if (checkPhotosPermissions(call)) { - Intent intent = new Intent(Intent.ACTION_PICK); - intent.setType("image/*"); - startActivityForResult(call, intent, REQUEST_IMAGE_PICK); - } - } - - public void processCameraImage(PluginCall call) { - if (imageFileSavePath == null) { - call.error(IMAGE_PROCESS_NO_FILE_ERROR); - return; - } - // Load the image as a Bitmap - File f = new File(imageFileSavePath); - BitmapFactory.Options bmOptions = new BitmapFactory.Options(); - Uri contentUri = Uri.fromFile(f); - Bitmap bitmap = BitmapFactory.decodeFile(imageFileSavePath, bmOptions); - - if (bitmap == null) { - call.error("User cancelled photos app"); - return; - } - - returnResult(call, bitmap, contentUri); - } - - public void processPickedImage(PluginCall call, Intent data) { - if (data == null) { - call.error("No image picked"); - return; - } - - Uri u = data.getData(); - - InputStream imageStream = null; - - try { - imageStream = getActivity().getContentResolver().openInputStream(u); - Bitmap bitmap = BitmapFactory.decodeStream(imageStream); - - if (bitmap == null) { - call.reject("Unable to process bitmap"); - return; - } - - returnResult(call, bitmap, u); - } catch (OutOfMemoryError err) { - call.error("Out of memory"); - } catch (FileNotFoundException ex) { - call.error("No such image found", ex); - } finally { - if (imageStream != null) { - try { - imageStream.close(); - } catch (IOException e) { - Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e); - } - } - } - } - - /** - * Save the modified image we've created to a temporary location, so we can - * return a URI to it later - * @param bitmap - * @param contentUri - * @param is - * @return - * @throws IOException - */ - private Uri saveTemporaryImage(Bitmap bitmap, Uri contentUri, InputStream is) throws IOException { - String filename = contentUri.getLastPathSegment(); - if (!filename.contains(".jpg") && !filename.contains(".jpeg")) { - filename += "." + (new java.util.Date()).getTime() + ".jpeg"; - } - File cacheDir = getActivity().getCacheDir(); - File outFile = new File(cacheDir, filename); - FileOutputStream fos = new FileOutputStream(outFile); - byte[] buffer = new byte[1024]; - int len; - while ((len = is.read(buffer)) != -1) { - fos.write(buffer, 0, len); - } - fos.close(); - return Uri.fromFile(outFile); - } - - /** - * After processing the image, return the final result back to the caller. - * @param call - * @param bitmap - * @param u - */ - private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { - try { - bitmap = prepareBitmap(bitmap, u); - } catch (IOException e) { - call.reject(UNABLE_TO_PROCESS_IMAGE); - return; - } - - ExifWrapper exif = ImageUtils.getExifData(getContext(), bitmap, u); - - // Compress the final image and prepare for output to client - ByteArrayOutputStream bitmapOutputStream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), bitmapOutputStream); - - if (settings.isAllowEditing() && !isEdited) { - editImage(call, bitmap, u, bitmapOutputStream); - return; - } - - boolean saveToGallery = call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY); - if (saveToGallery && (imageEditedFileSavePath != null || imageFileSavePath != null)) { - try { - String fileToSavePath = imageEditedFileSavePath != null ? imageEditedFileSavePath : imageFileSavePath; - File fileToSave = new File(fileToSavePath); - MediaStore.Images.Media.insertImage(getActivity().getContentResolver(), fileToSavePath, fileToSave.getName(), ""); - } catch (FileNotFoundException e) { - Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e); - } - } - - if (settings.getResultType() == CameraResultType.BASE64) { - returnBase64(call, exif, bitmapOutputStream); - } else if (settings.getResultType() == CameraResultType.URI) { - returnFileURI(call, exif, bitmap, u, bitmapOutputStream); - } else if (settings.getResultType() == CameraResultType.DATAURL) { - returnDataUrl(call, exif, bitmapOutputStream); - } else { - call.reject(INVALID_RESULT_TYPE_ERROR); - } - - // Result returned, clear stored paths - imageFileSavePath = null; - imageFileUri = null; - imageEditedFileSavePath = null; - } - - private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri u, ByteArrayOutputStream bitmapOutputStream) { - Uri newUri = getTempImage(bitmap, u, bitmapOutputStream); - if (newUri != null) { - JSObject ret = new JSObject(); - ret.put("format", "jpeg"); - ret.put("exif", exif.toJson()); - ret.put("path", newUri.toString()); - ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri)); - call.resolve(ret); - } else { - call.reject(UNABLE_TO_PROCESS_IMAGE); - } - } - - private Uri getTempImage(Bitmap bitmap, Uri u, ByteArrayOutputStream bitmapOutputStream) { - ByteArrayInputStream bis = null; - Uri newUri = null; - try { - bis = new ByteArrayInputStream(bitmapOutputStream.toByteArray()); - newUri = saveTemporaryImage(bitmap, u, bis); - } catch (IOException ex) {} finally { - if (bis != null) { - try { - bis.close(); - } catch (IOException e) { - Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e); - } - } - } - return newUri; - } - - /** - * Apply our standard processing of the bitmap, returning a new one and - * recycling the old one in the process - * @param bitmap - * @param imageUri - * @return - */ - private Bitmap prepareBitmap(Bitmap bitmap, Uri imageUri) throws IOException { - if (settings.isShouldCorrectOrientation()) { - final Bitmap newBitmap = ImageUtils.correctOrientation(getContext(), bitmap, imageUri); - bitmap = replaceBitmap(bitmap, newBitmap); - } - - if (settings.isShouldResize()) { - final Bitmap newBitmap = ImageUtils.resize( - bitmap, - settings.getWidth(), - settings.getHeight(), - settings.getPreserveAspectRatio() - ); - bitmap = replaceBitmap(bitmap, newBitmap); - } - - return bitmap; - } - - private Bitmap replaceBitmap(Bitmap bitmap, final Bitmap newBitmap) { - if (bitmap != newBitmap) { - bitmap.recycle(); - } - bitmap = newBitmap; - return bitmap; - } - - private void returnDataUrl(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { - byte[] byteArray = bitmapOutputStream.toByteArray(); - String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); - - JSObject data = new JSObject(); - data.put("format", "jpeg"); - data.put("dataUrl", "data:image/jpeg;base64," + encoded); - data.put("exif", exif.toJson()); - call.resolve(data); - } - - private void returnBase64(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { - byte[] byteArray = bitmapOutputStream.toByteArray(); - String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); - - JSObject data = new JSObject(); - data.put("format", "jpeg"); - data.put("base64String", encoded); - data.put("exif", exif.toJson()); - call.resolve(data); - } - - @Override - protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.handleRequestPermissionsResult(requestCode, permissions, grantResults); - - Logger.debug(getLogTag(), "handling request perms result"); - - if (getSavedCall() == null) { - Logger.debug(getLogTag(), "No stored plugin call for permissions request result"); - return; - } - - PluginCall savedCall = getSavedCall(); - - for (int i = 0; i < grantResults.length; i++) { - int result = grantResults[i]; - String perm = permissions[i]; - if (result == PackageManager.PERMISSION_DENIED) { - Logger.debug(getLogTag(), "User denied camera permission: " + perm); - savedCall.error(PERMISSION_DENIED_ERROR); - return; - } - } - - if (requestCode == REQUEST_IMAGE_CAPTURE) { - doShow(savedCall); - } - } - - @Override - protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) { - super.handleOnActivityResult(requestCode, resultCode, data); - - PluginCall savedCall = getSavedCall(); - - if (savedCall == null) { - return; - } - - settings = getSettings(savedCall); - - if (requestCode == REQUEST_IMAGE_CAPTURE) { - processCameraImage(savedCall); - } else if (requestCode == REQUEST_IMAGE_PICK) { - processPickedImage(savedCall, data); - } else if (requestCode == REQUEST_IMAGE_EDIT && resultCode == Activity.RESULT_OK) { - isEdited = true; - processPickedImage(savedCall, data); - } else if (resultCode == Activity.RESULT_CANCELED && imageFileSavePath != null) { - imageEditedFileSavePath = null; - isEdited = true; - processCameraImage(savedCall); - } - } - - private void editImage(PluginCall call, Bitmap bitmap, Uri uri, ByteArrayOutputStream bitmapOutputStream) { - Uri origPhotoUri = uri; - if (imageFileUri != null) { - origPhotoUri = imageFileUri; - } - try { - Intent editIntent = createEditIntent(origPhotoUri, false); - startActivityForResult(call, editIntent, REQUEST_IMAGE_EDIT); - } catch (SecurityException ex) { - Uri tempImage = getTempImage(bitmap, uri, bitmapOutputStream); - Intent editIntent = createEditIntent(tempImage, true); - if (editIntent != null) { - startActivityForResult(call, editIntent, REQUEST_IMAGE_EDIT); - } else { - call.error(IMAGE_EDIT_ERROR); - } - } catch (Exception ex) { - call.error(IMAGE_EDIT_ERROR, ex); - } - } - - private Intent createEditIntent(Uri origPhotoUri, boolean expose) { - Uri editUri = origPhotoUri; - try { - if (expose) { - editUri = - FileProvider.getUriForFile( - getActivity(), - getContext().getPackageName() + ".fileprovider", - new File(origPhotoUri.getPath()) - ); - } - Intent editIntent = new Intent(Intent.ACTION_EDIT); - editIntent.setDataAndType(editUri, "image/*"); - File editedFile = CameraUtils.createImageFile(getActivity()); - imageEditedFileSavePath = editedFile.getAbsolutePath(); - Uri editedUri = Uri.fromFile(editedFile); - editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - editIntent.putExtra(MediaStore.EXTRA_OUTPUT, editedUri); - return editIntent; - } catch (Exception ex) { - return null; - } - } - - @Override - protected Bundle saveInstanceState() { - Bundle bundle = super.saveInstanceState(); - bundle.putString("cameraImageFileSavePath", imageFileSavePath); - return bundle; - } - - @Override - protected void restoreState(Bundle state) { - String storedImageFileSavePath = state.getString("cameraImageFileSavePath"); - if (storedImageFileSavePath != null) { - imageFileSavePath = storedImageFileSavePath; - } - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraResultType.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraResultType.java deleted file mode 100644 index 5838c10572..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraResultType.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.getcapacitor.plugin.camera; - -public enum CameraResultType { - BASE64("base64"), - URI("uri"), - DATAURL("dataUrl"); - - private String type; - - CameraResultType(String type) { - this.type = type; - } - - public String getType() { - return type; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java deleted file mode 100644 index f1fbc90213..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSettings.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.getcapacitor.plugin.camera; - -public class CameraSettings { - public static final int DEFAULT_QUALITY = 90; - public static final boolean DEFAULT_SAVE_IMAGE_TO_GALLERY = false; - public static final boolean DEFAULT_CORRECT_ORIENTATION = true; - - private CameraResultType resultType = CameraResultType.BASE64; - private int quality = DEFAULT_QUALITY; - private boolean shouldResize = false; - private boolean shouldCorrectOrientation = DEFAULT_CORRECT_ORIENTATION; - private boolean saveToGallery = DEFAULT_SAVE_IMAGE_TO_GALLERY; - private boolean allowEditing = false; - private int width = 0; - private int height = 0; - private CameraSource source = CameraSource.PROMPT; - private boolean preserveAspectRatio = false; - - public CameraResultType getResultType() { - return resultType; - } - - public void setResultType(CameraResultType resultType) { - this.resultType = resultType; - } - - public int getQuality() { - return quality; - } - - public void setQuality(int quality) { - this.quality = quality; - } - - public boolean isShouldResize() { - return shouldResize; - } - - public void setShouldResize(boolean shouldResize) { - this.shouldResize = shouldResize; - } - - public boolean isShouldCorrectOrientation() { - return shouldCorrectOrientation; - } - - public void setShouldCorrectOrientation(boolean shouldCorrectOrientation) { - this.shouldCorrectOrientation = shouldCorrectOrientation; - } - - public boolean isSaveToGallery() { - return saveToGallery; - } - - public void setSaveToGallery(boolean saveToGallery) { - this.saveToGallery = saveToGallery; - } - - public boolean isAllowEditing() { - return allowEditing; - } - - public void setAllowEditing(boolean allowEditing) { - this.allowEditing = allowEditing; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - - public CameraSource getSource() { - return source; - } - - public void setSource(CameraSource source) { - this.source = source; - } - - public void setPreserveAspectRatio(boolean preserveAspectRatio) { - this.preserveAspectRatio = preserveAspectRatio; - } - - public boolean getPreserveAspectRatio() { - return this.preserveAspectRatio; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSource.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSource.java deleted file mode 100644 index cfcb165b52..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraSource.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.getcapacitor.plugin.camera; - -public enum CameraSource { - PROMPT("PROMPT"), - CAMERA("CAMERA"), - PHOTOS("PHOTOS"); - - private String source; - - CameraSource(String source) { - this.source = source; - } - - public String getSource() { - return this.source; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraUtils.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraUtils.java deleted file mode 100644 index 7512d843fa..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/CameraUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.getcapacitor.plugin.camera; - -import android.app.Activity; -import android.net.Uri; -import android.os.Environment; -import androidx.core.content.FileProvider; -import com.getcapacitor.Logger; -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; - -public class CameraUtils { - - public static Uri createImageFileUri(Activity activity, String appId) throws IOException { - File photoFile = CameraUtils.createImageFile(activity); - return FileProvider.getUriForFile(activity, appId + ".fileprovider", photoFile); - } - - public static File createImageFile(Activity activity) throws IOException { - // Create an image file name - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - String imageFileName = "JPEG_" + timeStamp + "_"; - File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES); - - File image = File.createTempFile(imageFileName, /* prefix */".jpg", /* suffix */storageDir/* directory */); - - return image; - } - - protected static String getLogTag() { - return Logger.tags("CameraUtils"); - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ExifWrapper.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ExifWrapper.java deleted file mode 100644 index 5aed00f420..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ExifWrapper.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.getcapacitor.plugin.camera; - -import static androidx.exifinterface.media.ExifInterface.*; - -import androidx.exifinterface.media.ExifInterface; -import com.getcapacitor.JSObject; - -public class ExifWrapper { - private final ExifInterface exif; - - public ExifWrapper(ExifInterface exif) { - this.exif = exif; - } - - public JSObject toJson() { - JSObject ret = new JSObject(); - - if (this.exif == null) { - return ret; - } - - // Commented fields are for API 24. Left in to save someone the wrist damage later - - p(ret, TAG_APERTURE_VALUE); - /* - p(ret, TAG_ARTIST); - p(ret, TAG_BITS_PER_SAMPLE); - p(ret, TAG_BRIGHTNESS_VALUE); - p(ret, TAG_CFA_PATTERN); - p(ret, TAG_COLOR_SPACE); - p(ret, TAG_COMPONENTS_CONFIGURATION); - p(ret, TAG_COMPRESSED_BITS_PER_PIXEL); - p(ret, TAG_COMPRESSION); - p(ret, TAG_CONTRAST); - p(ret, TAG_COPYRIGHT); - */ - p(ret, TAG_DATETIME); - /* - p(ret, TAG_DATETIME_DIGITIZED); - p(ret, TAG_DATETIME_ORIGINAL); - p(ret, TAG_DEFAULT_CROP_SIZE); - p(ret, TAG_DEVICE_SETTING_DESCRIPTION); - p(ret, TAG_DIGITAL_ZOOM_RATIO); - p(ret, TAG_DNG_VERSION); - p(ret, TAG_EXIF_VERSION); - p(ret, TAG_EXPOSURE_BIAS_VALUE); - p(ret, TAG_EXPOSURE_INDEX); - p(ret, TAG_EXIF_VERSION); - p(ret, TAG_EXPOSURE_MODE); - p(ret, TAG_EXPOSURE_PROGRAM); - */ - p(ret, TAG_EXPOSURE_TIME); - // p(ret, TAG_F_NUMBER); - // p(ret, TAG_FILE_SOURCE); - p(ret, TAG_FLASH); - // p(ret, TAG_FLASH_ENERGY); - // p(ret, TAG_FLASHPIX_VERSION); - p(ret, TAG_FOCAL_LENGTH); - // p(ret, TAG_FOCAL_LENGTH_IN_35MM_FILM); - // p(ret, TAG_FOCAL_PLANE_RESOLUTION_UNIT); - p(ret, TAG_FOCAL_LENGTH); - // p(ret, TAG_GAIN_CONTROL); - p(ret, TAG_GPS_LATITUDE); - p(ret, TAG_GPS_LATITUDE_REF); - p(ret, TAG_GPS_LONGITUDE); - p(ret, TAG_GPS_LONGITUDE_REF); - p(ret, TAG_GPS_ALTITUDE); - p(ret, TAG_GPS_ALTITUDE_REF); - // p(ret, TAG_GPS_AREA_INFORMATION); - p(ret, TAG_GPS_DATESTAMP); - /* - API 24 - p(ret, TAG_GPS_DEST_BEARING); - p(ret, TAG_GPS_DEST_BEARING_REF); - p(ret, TAG_GPS_DEST_DISTANCE_REF); - p(ret, TAG_GPS_DEST_DISTANCE_REF); - p(ret, TAG_GPS_DEST_LATITUDE); - p(ret, TAG_GPS_DEST_LATITUDE_REF); - p(ret, TAG_GPS_DEST_LONGITUDE); - p(ret, TAG_GPS_DEST_LONGITUDE_REF); - p(ret, TAG_GPS_DIFFERENTIAL); - p(ret, TAG_GPS_DOP); - p(ret, TAG_GPS_IMG_DIRECTION); - p(ret, TAG_GPS_IMG_DIRECTION_REF); - p(ret, TAG_GPS_MAP_DATUM); - p(ret, TAG_GPS_MEASURE_MODE); - */ - p(ret, TAG_GPS_PROCESSING_METHOD); - /* - API 24 - p(ret, TAG_GPS_SATELLITES); - p(ret, TAG_GPS_SPEED); - p(ret, TAG_GPS_SPEED_REF); - p(ret, TAG_GPS_STATUS); - */ - p(ret, TAG_GPS_TIMESTAMP); - /* - API 24 - p(ret, TAG_GPS_TRACK); - p(ret, TAG_GPS_TRACK_REF); - p(ret, TAG_GPS_VERSION_ID); - p(ret, TAG_IMAGE_DESCRIPTION); - */ - p(ret, TAG_IMAGE_LENGTH); - // p(ret, TAG_IMAGE_UNIQUE_ID); - p(ret, TAG_IMAGE_WIDTH); - p(ret, TAG_ISO_SPEED); - /* - p(ret, TAG_INTEROPERABILITY_INDEX); - p(ret, TAG_ISO_SPEED_RATINGS); - p(ret, TAG_JPEG_INTERCHANGE_FORMAT); - p(ret, TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); - p(ret, TAG_LIGHT_SOURCE); - */ - p(ret, TAG_MAKE); - /* - p(ret, TAG_MAKER_NOTE); - p(ret, TAG_MAX_APERTURE_VALUE); - p(ret, TAG_METERING_MODE); - */ - p(ret, TAG_MODEL); - /* - p(ret, TAG_NEW_SUBFILE_TYPE); - p(ret, TAG_OECF); - p(ret, TAG_ORF_ASPECT_FRAME); - p(ret, TAG_ORF_PREVIEW_IMAGE_LENGTH); - p(ret, TAG_ORF_PREVIEW_IMAGE_START); - */ - p(ret, TAG_ORIENTATION); - /* - p(ret, TAG_ORF_THUMBNAIL_IMAGE); - p(ret, TAG_PHOTOMETRIC_INTERPRETATION); - p(ret, TAG_PIXEL_X_DIMENSION); - p(ret, TAG_PIXEL_Y_DIMENSION); - p(ret, TAG_PLANAR_CONFIGURATION); - p(ret, TAG_PRIMARY_CHROMATICITIES); - p(ret, TAG_REFERENCE_BLACK_WHITE); - p(ret, TAG_RELATED_SOUND_FILE); - p(ret, TAG_RESOLUTION_UNIT); - p(ret, TAG_ROWS_PER_STRIP); - p(ret, TAG_RW2_ISO); - p(ret, TAG_RW2_JPG_FROM_RAW); - */ - p(ret, TAG_WHITE_BALANCE); - - return ret; - } - - public void p(JSObject o, String tag) { - String val = exif.getAttribute(tag); - o.put(tag, val); - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java deleted file mode 100644 index 17a2c65e64..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/camera/ImageUtils.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.getcapacitor.plugin.camera; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.net.Uri; -import android.os.Build; -import android.provider.MediaStore; -import androidx.exifinterface.media.ExifInterface; -import com.getcapacitor.FileUtils; -import com.getcapacitor.Logger; -import java.io.IOException; -import java.io.InputStream; - -public class ImageUtils { - - /** - * Resize an image to the given width and height. - * @param bitmap - * @param width - * @param height - * @return a new, scaled Bitmap - */ - public static Bitmap resize(Bitmap bitmap, final int width, final int height) { - return ImageUtils.resize(bitmap, width, height, false); - } - - /** - * Resize an image to the given width and height considering the preserveAspectRatio flag. - * @param bitmap - * @param width - * @param height - * @param preserveAspectRatio - * @return a new, scaled Bitmap - */ - public static Bitmap resize(Bitmap bitmap, final int width, final int height, final boolean preserveAspectRatio) { - if (preserveAspectRatio) { - return ImageUtils.resizePreservingAspectRatio(bitmap, width, height); - } - return ImageUtils.resizeImageWithoutPreservingAspectRatio(bitmap, width, height); - } - - /** - * Resize an image to the given width and height. Leave one dimension 0 to - * perform an aspect-ratio scale on the provided dimension. - * @param bitmap - * @param width - * @param height - * @return a new, scaled Bitmap - */ - private static Bitmap resizeImageWithoutPreservingAspectRatio(Bitmap bitmap, final int width, final int height) { - float aspect = bitmap.getWidth() / (float) bitmap.getHeight(); - if (width > 0 && height > 0) { - return Bitmap.createScaledBitmap(bitmap, width, height, false); - } else if (width > 0) { - return Bitmap.createScaledBitmap(bitmap, width, (int) (width * 1 / aspect), false); - } else if (height > 0) { - return Bitmap.createScaledBitmap(bitmap, (int) (height * aspect), height, false); - } - - return bitmap; - } - - /** - * Resize an image to the given max width and max height. Constraint can be put - * on one dimension, or both. Resize will always preserve aspect ratio. - * @param bitmap - * @param desiredMaxWidth - * @param desiredMaxHeight - * @return a new, scaled Bitmap - */ - private static Bitmap resizePreservingAspectRatio(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - - // 0 is treated as 'no restriction' - int maxHeight = desiredMaxHeight == 0 ? height : desiredMaxHeight; - int maxWidth = desiredMaxWidth == 0 ? width : desiredMaxWidth; - - // resize with preserved aspect ratio - float newWidth = Math.min(width, maxWidth); - float newHeight = (height * newWidth) / width; - - if (newHeight > maxHeight) { - newWidth = (width * maxHeight) / height; - newHeight = maxHeight; - } - return Bitmap.createScaledBitmap(bitmap, Math.round(newWidth), Math.round(newHeight), false); - } - - /** - * Transform an image with the given matrix - * @param bitmap - * @param matrix - * @return - */ - private static Bitmap transform(final Bitmap bitmap, final Matrix matrix) { - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - - /** - * Correct the orientation of an image by reading its exif information and rotating - * the appropriate amount for portrait mode - * @param bitmap - * @param imageUri - * @return - */ - public static Bitmap correctOrientation(final Context c, final Bitmap bitmap, final Uri imageUri) throws IOException { - if (Build.VERSION.SDK_INT < 24) { - return correctOrientationOlder(c, bitmap, imageUri); - } else { - final int orientation = getOrientation(c, imageUri); - - if (orientation != 0) { - Matrix matrix = new Matrix(); - matrix.postRotate(orientation); - - return transform(bitmap, matrix); - } else { - return bitmap; - } - } - } - - private static Bitmap correctOrientationOlder(final Context c, final Bitmap bitmap, final Uri imageUri) { - // TODO: To be tested on older phone using Android API < 24 - - String[] orientationColumn = { MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION }; - Cursor cur = c.getContentResolver().query(imageUri, orientationColumn, null, null, null); - int orientation = -1; - if (cur != null && cur.moveToFirst()) { - orientation = cur.getInt(cur.getColumnIndex(orientationColumn[0])); - } - Matrix matrix = new Matrix(); - - if (orientation != -1) { - matrix.postRotate(orientation); - } - - return transform(bitmap, matrix); - } - - private static int getOrientation(final Context c, final Uri imageUri) throws IOException { - int result = 0; - - try (InputStream iStream = c.getContentResolver().openInputStream(imageUri)) { - final ExifInterface exifInterface = new ExifInterface(iStream); - - final int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); - - if (orientation == ExifInterface.ORIENTATION_ROTATE_90) { - result = 90; - } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) { - result = 180; - } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) { - result = 270; - } - } - - return result; - } - - public static ExifWrapper getExifData(final Context c, final Bitmap bitmap, final Uri imageUri) { - try { - String fu = FileUtils.getFileUrlForUri(c, imageUri); - final ExifInterface exifInterface = new ExifInterface(fu); - - return new ExifWrapper(exifInterface); - } catch (IOException ex) { - Logger.error("Error loading exif data from image", ex); - } finally {} - return new ExifWrapper(null); - } -} diff --git a/core/src/core-plugin-definitions.ts b/core/src/core-plugin-definitions.ts index 110a8a3eb1..10b9c54615 100644 --- a/core/src/core-plugin-definitions.ts +++ b/core/src/core-plugin-definitions.ts @@ -4,7 +4,6 @@ import { Plugin, PluginListenerHandle } from './definitions'; export interface PluginRegistry { App: AppPlugin; BackgroundTask: BackgroundTaskPlugin; - Camera: CameraPlugin; Geolocation: GeolocationPlugin; Keyboard: KeyboardPlugin; LocalNotifications: LocalNotificationsPlugin; @@ -189,150 +188,6 @@ export interface BackgroundTaskPlugin extends Plugin { // -export interface CameraPlugin extends Plugin { - /** - * Prompt the user to pick a photo from an album, or take a new photo - * with the camera. - */ - getPhoto(options: CameraOptions): Promise; -} - -export interface CameraOptions { - /** - * The quality of image to return as JPEG, from 0-100 - */ - quality?: number; - /** - * Whether to allow the user to crop or make small edits (platform specific) - */ - allowEditing?: boolean; - /** - * How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported - */ - resultType: CameraResultType; - /** - * Whether to save the photo to the gallery. - * If the photo was picked from the gallery, it will only be saved if edited. - * Default: false - */ - saveToGallery?: boolean; - /** - * The width of the saved image - */ - width?: number; - /** - * The height of the saved image - */ - height?: number; - /** - * Whether to preserve the aspect ratio of the image. - * If this flag is true, the width and height will be used as max values - * and the aspect ratio will be preserved. This is only relevant when - * both a width and height are passed. When only width or height is provided - * the aspect ratio is always preserved (and this option is a no-op). - * - * A future major version will change this behavior to be default, - * and may also remove this option altogether. - * Default: false - */ - preserveAspectRatio?: boolean; - /** - * Whether to automatically rotate the image "up" to correct for orientation - * in portrait mode - * Default: true - */ - correctOrientation?: boolean; - /** - * The source to get the photo from. By default this prompts the user to select - * either the photo album or take a photo. - * Default: CameraSource.Prompt - */ - source?: CameraSource; - /** - * iOS and Web only: The camera direction. - * Default: CameraDirection.Rear - */ - direction?: CameraDirection; - - /** - * iOS only: The presentation style of the Camera. Defaults to fullscreen. - */ - presentationStyle?: 'fullscreen' | 'popover'; - - /** - * Web only: Whether to use the PWA Element experience or file input. The - * default is to use PWA Elements if installed and fall back to file input. - * To always use file input, set this to `true`. - * - * Learn more about PWA Elements: https://capacitorjs.com/docs/pwa-elements - */ - webUseInput?: boolean; - - /** - * If use CameraSource.Prompt only, can change Prompt label. - * default: - * promptLabelHeader : 'Photo' // iOS only - * promptLabelCancel : 'Cancel' // iOS only - * promptLabelPhoto : 'From Photos' - * promptLabelPicture : 'Take Picture' - */ - promptLabelHeader?: string; - promptLabelCancel?: string; - promptLabelPhoto?: string; - promptLabelPicture?: string; -} - -export enum CameraSource { - Prompt = 'PROMPT', - Camera = 'CAMERA', - Photos = 'PHOTOS', -} - -export enum CameraDirection { - Rear = 'REAR', - Front = 'FRONT', -} - -export interface CameraPhoto { - /** - * The base64 encoded string representation of the image, if using CameraResultType.Base64. - */ - base64String?: string; - /** - * The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl. - */ - dataUrl?: string; - /** - * If using CameraResultType.Uri, the path will contain a full, - * platform-specific file URL that can be read later using the Filsystem API. - */ - path?: string; - /** - * webPath returns a path that can be used to set the src attribute of an image for efficient - * loading and rendering. - */ - webPath?: string; - /** - * Exif data, if any, retrieved from the image - */ - exif?: any; - /** - * The format of the image, ex: jpeg, png, gif. - * - * iOS and Android only support jpeg. - * Web supports jpeg and png. gif is only supported if using file input. - */ - format: string; -} - -export enum CameraResultType { - Uri = 'uri', - Base64 = 'base64', - DataUrl = 'dataUrl', -} - -// - export interface GeolocationPlugin extends Plugin { /** * Get the current GPS location of the device diff --git a/core/src/web-plugins.ts b/core/src/web-plugins.ts index 087508b416..dc95c2386f 100644 --- a/core/src/web-plugins.ts +++ b/core/src/web-plugins.ts @@ -1,20 +1,17 @@ import { mergeWebPlugin } from './plugins'; import { App } from './web/app'; -import { Camera } from './web/camera'; import { Geolocation } from './web/geolocation'; import { LocalNotifications } from './web/local-notifications'; import { Modals } from './web/modals'; import { SplashScreen } from './web/splash-screen'; export * from './web/app'; -export * from './web/camera'; export * from './web/geolocation'; export * from './web/local-notifications'; export * from './web/modals'; export * from './web/splash-screen'; mergeWebPlugin(App); -mergeWebPlugin(Camera); mergeWebPlugin(Geolocation); mergeWebPlugin(LocalNotifications); mergeWebPlugin(Modals); diff --git a/ios/Capacitor/Capacitor/Plugins/Camera.swift b/ios/Capacitor/Capacitor/Plugins/Camera.swift deleted file mode 100644 index 26933389c7..0000000000 --- a/ios/Capacitor/Capacitor/Plugins/Camera.swift +++ /dev/null @@ -1,423 +0,0 @@ -import Foundation -import Photos - -enum CameraSource: String { - case prompt = "PROMPT" - case camera = "CAMERA" - case photos = "PHOTOS" -} - -enum CameraDirection: String { - case rear = "REAR" - case front = "FRONT" -} - -enum CameraResultType: String { - case base64 = "base64" - case uri = "uri" - case dataURL = "dataUrl" -} - -struct CameraSettings { - var source: CameraSource = CameraSource.prompt - var direction: CameraDirection = CameraDirection.rear - var allowEditing = false - var shouldResize = false - var shouldCorrectOrientation = true - var quality: Float = 1.0 - var width: Float = 0 - var height: Float = 0 - var resultType = "base64" - var saveToGallery = false - var preserveAspectRatio = false -} - -@objc(CAPCameraPlugin) -public class CAPCameraPlugin: CAPPlugin, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate { - let defaultSource = CameraSource.prompt - let defaultDirection = CameraDirection.rear - - var imagePicker: UIImagePickerController? - var call: CAPPluginCall? - - var imageCounter = 0 - - var settings = CameraSettings() - - @objc func getPhoto(_ call: CAPPluginCall) { - self.call = call - self.settings = getSettings(call) - - // Make sure they have all the necessary info.plist settings - if let missingUsageDescription = checkUsageDescriptions() { - bridge?.modulePrint(self, missingUsageDescription) - call.error(missingUsageDescription) - bridge?.alert("Camera Error", "Missing required usage description. See console for more information") - return - } - - DispatchQueue.main.async { - self.imagePicker = UIImagePickerController() - self.imagePicker!.delegate = self - self.imagePicker!.allowsEditing = self.settings.allowEditing - } - - doShow(call: call, settings: settings) - } - - func getSettings(_ call: CAPPluginCall) -> CameraSettings { - var settings = CameraSettings() - settings.quality = call.get("quality", Float.self, 100)! - settings.allowEditing = call.get("allowEditing", Bool.self, false)! - settings.source = CameraSource(rawValue: call.getString("source") ?? defaultSource.rawValue) ?? defaultSource - settings.direction = CameraDirection(rawValue: call.getString("direction") ?? defaultDirection.rawValue) ?? defaultDirection - settings.resultType = call.get("resultType", String.self, "base64")! - settings.saveToGallery = call.get("saveToGallery", Bool.self, false)! - settings.preserveAspectRatio = call.get("preserveAspectRatio", Bool.self, false)! - - // Get the new image dimensions if provided - settings.width = Float(call.get("width", Int.self, 0)!) - settings.height = Float(call.get("height", Int.self, 0)!) - if settings.width > 0 || settings.height > 0 { - // We resize only if a dimension was provided - settings.shouldResize = true - } - settings.shouldCorrectOrientation = call.get("correctOrientation", Bool.self, true)! - - return settings - } - - func doShow(call: CAPPluginCall, settings: CameraSettings) { - - DispatchQueue.main.async { - switch settings.source { - case CameraSource.prompt: - self.showPrompt(call) - case CameraSource.camera: - self.showCamera(call) - case CameraSource.photos: - self.showPhotos(call) - } - } - } - - func showPrompt(_ call: CAPPluginCall) { - // Build the action sheet - let promptLabelHeader = call.getString("promptLabelHeader") ?? "Photo" - let promptLabelPhoto = call.getString("promptLabelPhoto") ?? "From Photos" - let promptLabelPicture = call.getString("promptLabelPicture") ?? "Take Picture" - let promptLabelCancel = call.getString("promptLabelCancel") ?? "Cancel" - - let alert = UIAlertController(title: promptLabelHeader, message: nil, preferredStyle: UIAlertController.Style.actionSheet) - alert.addAction(UIAlertAction(title: promptLabelPhoto, style: .default, handler: { (_: UIAlertAction) in - self.showPhotos(call) - })) - - alert.addAction(UIAlertAction(title: promptLabelPicture, style: .default, handler: { (_: UIAlertAction) in - self.showCamera(call) - })) - - alert.addAction(UIAlertAction(title: promptLabelCancel, style: .cancel, handler: { (_: UIAlertAction) in - self.call?.error("User cancelled photos app") - })) - - self.setCenteredPopover(alert) - self.bridge?.viewController?.present(alert, animated: true, completion: nil) - } - - func showCamera(_ call: CAPPluginCall) { - if (self.bridge?.isSimulator() ?? false) || !UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) { - self.bridge?.modulePrint(self, "Camera not available in simulator") - self.bridge?.alert("Camera Error", "Camera not available in Simulator") - call.error("Camera not available while running in Simulator") - return - } - - AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in - if granted { - DispatchQueue.main.async { - guard let strongSelf = self else { - return - } - let presentationStyle = call.getString("presentationStyle") - if presentationStyle != nil && presentationStyle == "popover" { - strongSelf.configurePicker() - } else { - strongSelf.imagePicker!.modalPresentationStyle = .fullScreen - } - - strongSelf.imagePicker!.sourceType = .camera - - if strongSelf.settings.direction.rawValue == "REAR" { - if UIImagePickerController.isCameraDeviceAvailable(.rear) { - strongSelf.imagePicker!.cameraDevice = .rear - } - } else if strongSelf.settings.direction.rawValue == "FRONT" { - if UIImagePickerController.isCameraDeviceAvailable(.front) { - strongSelf.imagePicker!.cameraDevice = .front - } - } - - strongSelf.bridge?.viewController?.present(strongSelf.imagePicker!, animated: true, completion: nil) - } - } else { - call.error("User denied access to camera") - } - } - } - - func showPhotos(_ call: CAPPluginCall) { - let photoAuthorizationStatus = PHPhotoLibrary.authorizationStatus() - if photoAuthorizationStatus != PHAuthorizationStatus.authorized { - PHPhotoLibrary.requestAuthorization({ (status) in - if status != PHAuthorizationStatus.authorized { - call.error("User denied access to photos") - return - } else { - DispatchQueue.main.async { - self.presentPhotos() - } - } - }) - } else { - presentPhotos() - } - } - - private func presentPhotos() { - self.configurePicker() - self.imagePicker!.sourceType = .photoLibrary - self.bridge?.viewController?.present(self.imagePicker!, animated: true, completion: nil) - } - - private func configurePicker() { - self.imagePicker!.modalPresentationStyle = .popover - self.imagePicker!.popoverPresentationController?.delegate = self - self.setCenteredPopover(self.imagePicker!) - } - - public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - picker.dismiss(animated: true) - self.call?.error("User cancelled photos app") - } - - public func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) { - self.call?.error("User cancelled photos app") - } - - public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - self.call?.error("User cancelled photos app") - } - - public func imagePickerController(_ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - var image: UIImage? - var isEdited = false - var isGallery = true - - if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { - // Use editedImage Here - isEdited = true - image = editedImage - } else if let originalImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { - // Use originalImage Here - image = originalImage - } - - var imageMetadata: [AnyHashable: Any] = [:] - if let photoMetadata = info[UIImagePickerController.InfoKey.mediaMetadata] as? [AnyHashable: Any] { - imageMetadata = photoMetadata - isGallery = false - } - if let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset { - imageMetadata = getImageMeta(asset: asset)! - } - - if settings.shouldResize { - guard let convertedImage = resizeImage(image!, settings.preserveAspectRatio) else { - self.call?.error("Error resizing image") - return - } - image = convertedImage - } - - if settings.shouldCorrectOrientation { - guard let convertedImage = correctOrientation(image!) else { - self.call?.error("Error resizing image") - return - } - image = convertedImage - } - - if settings.saveToGallery { - if !isGallery || isEdited { - UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil) - } - } - - guard let jpeg = image!.jpegData(compressionQuality: CGFloat(settings.quality/100)) else { - self.call?.error("Unable to convert image to jpeg") - return - } - - if settings.resultType == CameraResultType.base64.rawValue { - let base64String = jpeg.base64EncodedString() - - self.call?.success([ - "base64String": base64String, - "exif": makeExif(imageMetadata) ?? [:], - "format": "jpeg" - ]) - } else if settings.resultType == CameraResultType.dataURL.rawValue { - let base64String = jpeg.base64EncodedString() - - self.call?.success([ - "dataUrl": "data:image/jpeg;base64," + base64String, - "exif": makeExif(imageMetadata) ?? [:], - "format": "jpeg" - ]) - } else if settings.resultType == CameraResultType.uri.rawValue { - guard let path = try? saveTemporaryImage(jpeg), let webPath = CAPFileManager.getPortablePath(host: bridge?.getLocalUrl() ?? "", uri: URL(string: path)) else { - call?.reject("Unable to get portable path to file") - return - } - call?.success([ - "path": path, - "exif": makeExif(imageMetadata) ?? [:], - "webPath": webPath, - "format": "jpeg" - ]) - } - - picker.dismiss(animated: true, completion: nil) - } - - func metadataFromImageData(data: NSData) -> [String: Any]? { - let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse] - if let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary), let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary) as? [String: Any] { - return metadata - } - return nil - } - - func getImageMeta(asset: PHAsset) -> [String: Any]? { - let options = PHImageRequestOptions() - options.isSynchronous = true - options.resizeMode = .none - options.isNetworkAccessAllowed = false - options.version = .current - var meta: [String: Any]? - _ = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, _, _, _) in - if let data = imageData { - meta = self.metadataFromImageData(data: data as NSData) - } - } - return meta - } - - func resizeImage(_ image: UIImage, _ preserveAspectRatio: Bool) -> UIImage? { - if preserveAspectRatio { - return resizeImagePreservingAspectRatio(image) - } - return resizeImageWithoutPreservingAspectRatio(image) - } - - func resizeImageWithoutPreservingAspectRatio(_ image: UIImage) -> UIImage? { - let isAspectScale = settings.width > 0 && settings.height == 0 || settings.height > 0 && settings.width == 0 - let aspect = Float(image.size.width / image.size.height) - - var size = CGSize.init(width: Int(settings.width), height: Int(settings.height)) - if isAspectScale { - if settings.width > 0 { - size = CGSize.init(width: Int(settings.width), height: Int(settings.width * (1/aspect))) - } else if settings.height > 0 { - size = CGSize.init(width: Int(settings.height * aspect), height: Int(settings.height)) - } - } - - UIGraphicsBeginImageContextWithOptions(size, false, 1.0) - image.draw(in: CGRect(origin: CGPoint.zero, size: size)) - - let scaledImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return scaledImage - } - - func resizeImagePreservingAspectRatio(_ image: UIImage) -> UIImage? { - let imageHeight = Float(image.size.height) - let imageWidth = Float(image.size.width) - - // 0 is treated as 'no restriction' - let maxHeight = settings.height == 0 ? imageHeight : settings.height - let maxWidth = settings.width == 0 ? imageWidth : settings.width - - // resize with preserved aspect ratio - var newWidth = min(imageWidth, maxWidth) - var newHeight = (imageHeight * newWidth) / imageWidth - if newHeight > maxHeight { - newWidth = (imageWidth * maxHeight) / imageHeight - newHeight = maxHeight - } - let size = CGSize.init(width: Int(newWidth), height: Int(newHeight)) - - UIGraphicsBeginImageContextWithOptions(size, false, 1.0) - image.draw(in: CGRect(origin: CGPoint.zero, size: size)) - - let scaledImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return scaledImage - } - - func makeExif(_ exif: [AnyHashable: Any]?) -> [AnyHashable: Any]? { - return exif?["{Exif}"] as? [AnyHashable: Any] - } - - func correctOrientation(_ image: UIImage) -> UIImage? { - UIGraphicsBeginImageContext(image.size) - image.draw(at: .zero) - let newImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return newImage ?? image - } - - func saveTemporaryImage(_ data: Data) throws -> String { - var url: URL - repeat { - imageCounter += 1 - url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("photo-\(imageCounter).jpg") - } while FileManager.default.fileExists(atPath: url.absoluteString) - - try data.write(to: url, options: .atomic) - return url.absoluteString - } - - /** - * Make sure the developer provided proper usage descriptions - * per apple's terms. - */ - func checkUsageDescriptions() -> String? { - if let dict = Bundle.main.infoDictionary { - let hasPhotoLibraryAddUsage = dict["NSPhotoLibraryAddUsageDescription"] != nil - if !hasPhotoLibraryAddUsage { - let docLink = DocLinks.NSPhotoLibraryAddUsageDescription - return "You are missing NSPhotoLibraryAddUsageDescription in your Info.plist file." + - " Camera will not function without it. Learn more: \(docLink.rawValue)" - } - let hasPhotoLibraryUsage = dict["NSPhotoLibraryUsageDescription"] != nil - if !hasPhotoLibraryUsage { - let docLink = DocLinks.NSPhotoLibraryUsageDescription - return "You are missing NSPhotoLibraryUsageDescription in your Info.plist file." + - " Camera will not function without it. Learn more: \(docLink.rawValue)" - } - let hasCameraUsage = dict["NSCameraUsageDescription"] != nil - if !hasCameraUsage { - let docLink = DocLinks.NSCameraUsageDescription - return "You are missing NSCameraUsageDescription in your Info.plist file." + - " Camera will not function without it. Learn more: \(docLink.rawValue)" - } - } - - return nil - } - -} diff --git a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m index 129054a9b1..f83ece045f 100644 --- a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m +++ b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m @@ -16,10 +16,6 @@ CAP_PLUGIN_METHOD(finish, CAPPluginReturnNone); ) -CAP_PLUGIN(CAPCameraPlugin, "Camera", - CAP_PLUGIN_METHOD(getPhoto, CAPPluginReturnPromise); -) - CAP_PLUGIN(CAPConsolePlugin, "Console", CAP_PLUGIN_METHOD(log, CAPPluginReturnNone); )