@capgo/camera-preview 7.4.0-beta.1 → 7.4.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,6 +92,52 @@ Then run
92
92
  npx cap sync
93
93
  ```
94
94
 
95
+ ## Optional Configuration
96
+
97
+ To use certain features of this plugin, you will need to add the following permissions/keys to your native project configurations.
98
+
99
+ ### Android
100
+
101
+ In your `android/app/src/main/AndroidManifest.xml`:
102
+
103
+ - **Audio Recording** (`disableAudio: false`):
104
+ ```xml
105
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
106
+ ```
107
+
108
+ - **Saving to Gallery** (`saveToGallery: true`):
109
+ ```xml
110
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
111
+ ```
112
+
113
+ - **Location in EXIF Data** (`withExifLocation: true`):
114
+ ```xml
115
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
116
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
117
+ ```
118
+
119
+ ### iOS
120
+
121
+ In your `ios/App/App/Info.plist`, you must provide descriptions for the permissions your app requires. The keys are added automatically, but you need to provide the `string` values.
122
+
123
+ - **Audio Recording** (`disableAudio: false`):
124
+ ```xml
125
+ <key>NSMicrophoneUsageDescription</key>
126
+ <string>To record audio with videos</string>
127
+ ```
128
+
129
+ - **Saving to Gallery** (`saveToGallery: true`):
130
+ ```xml
131
+ <key>NSPhotoLibraryAddUsageDescription</key>
132
+ <string>To save photos to your gallery</string>
133
+ ```
134
+
135
+ - **Location in EXIF Data** (`withExifLocation: true`):
136
+ ```xml
137
+ <key>NSLocationWhenInUseUsageDescription</key>
138
+ <string>To add location data to your photos</string>
139
+ ```
140
+
95
141
  ## Extra Android installation steps
96
142
 
97
143
  **Important** `camera-preview` 3+ requires Gradle 7.
@@ -233,7 +279,7 @@ Stops the camera preview.
233
279
  ### capture(...)
234
280
 
235
281
  ```typescript
236
- capture(options: CameraPreviewPictureOptions) => Promise<{ value: string; }>
282
+ capture(options: CameraPreviewPictureOptions) => Promise<{ value: string; exif: ExifData; }>
237
283
  ```
238
284
 
239
285
  Captures a picture from the camera.
@@ -242,7 +288,7 @@ Captures a picture from the camera.
242
288
  | ------------- | ----------------------------------------------------------------------------------- | ---------------------------------------- |
243
289
  | **`options`** | <code><a href="#camerapreviewpictureoptions">CameraPreviewPictureOptions</a></code> | - The options for capturing the picture. |
244
290
 
245
- **Returns:** <code>Promise&lt;{ value: string; }&gt;</code>
291
+ **Returns:** <code>Promise&lt;{ value: string; exif: <a href="#exifdata">ExifData</a>; }&gt;</code>
246
292
 
247
293
  **Since:** 0.0.1
248
294
 
@@ -538,7 +584,7 @@ Defines the configuration options for starting the camera preview.
538
584
  | **`storeToFile`** | <code>boolean</code> | If true, saves the captured image to a file and returns the file path. If false, returns a base64 encoded string. | <code>false</code> |
539
585
  | **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> |
540
586
  | **`enableHighResolution`** | <code>boolean</code> | If true, enables high-resolution image capture. | <code>false</code> |
541
- | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>false</code> |
587
+ | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> |
542
588
  | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> |
543
589
  | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> |
544
590
  | **`enableZoom`** | <code>boolean</code> | If true, enables pinch-to-zoom functionality on the preview. | <code>false</code> |
@@ -546,16 +592,23 @@ Defines the configuration options for starting the camera preview.
546
592
  | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | |
547
593
 
548
594
 
595
+ #### ExifData
596
+
597
+ Represents EXIF data extracted from an image.
598
+
599
+
549
600
  #### CameraPreviewPictureOptions
550
601
 
551
602
  Defines the options for capturing a picture.
552
603
 
553
- | Prop | Type | Description | Default |
554
- | ------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------- |
555
- | **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | |
556
- | **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | |
557
- | **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> |
558
- | **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> |
604
+ | Prop | Type | Description | Default | Since |
605
+ | ---------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ----- |
606
+ | **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | | |
607
+ | **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | | |
608
+ | **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> | |
609
+ | **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> | |
610
+ | **`saveToGallery`** | <code>boolean</code> | If true, the captured image will be saved to the user's gallery. | <code>false</code> | 7.5.0 |
611
+ | **`withExifLocation`** | <code>boolean</code> | If true, the plugin will attempt to add GPS location data to the image's EXIF metadata. This may prompt the user for location permissions. | <code>false</code> | 7.6.0 |
559
612
 
560
613
 
561
614
  #### CameraSampleOptions
@@ -49,7 +49,8 @@ dependencies {
49
49
  implementation project(':capacitor-android')
50
50
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
51
51
  implementation 'androidx.exifinterface:exifinterface:1.4.1'
52
-
52
+ implementation 'com.google.android.gms:play-services-location:21.3.0'
53
+
53
54
  // CameraX dependencies
54
55
  def camerax_version = "1.5.0-beta01"
55
56
  implementation "androidx.camera:camera-core:${camerax_version}"
@@ -1,5 +1,7 @@
1
1
 
2
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
- >
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <uses-permission android:name="android.permission.CAMERA" />
4
+ <uses-feature android:name="android.hardware.camera" />
5
+ <uses-feature android:name="android.hardware.camera.autofocus" />
4
6
  </manifest>
5
7
 
@@ -3,15 +3,12 @@ package com.ahm.capacitor.camera.preview;
3
3
  import static android.Manifest.permission.CAMERA;
4
4
  import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
+ import android.Manifest;
6
7
  import android.content.pm.ActivityInfo;
7
- import android.graphics.Point;
8
8
  import android.util.DisplayMetrics;
9
9
  import android.util.TypedValue;
10
- import android.view.Display;
11
- import androidx.annotation.NonNull;
12
10
  import com.getcapacitor.JSArray;
13
11
  import com.getcapacitor.JSObject;
14
- import com.getcapacitor.Logger;
15
12
  import com.getcapacitor.PermissionState;
16
13
  import com.getcapacitor.Plugin;
17
14
  import com.getcapacitor.PluginCall;
@@ -27,6 +24,11 @@ import java.util.Objects;
27
24
  import android.util.Size;
28
25
  import android.util.Log;
29
26
  import com.ahm.capacitor.camera.preview.model.LensInfo;
27
+ import com.google.android.gms.location.FusedLocationProviderClient;
28
+ import com.google.android.gms.location.LocationServices;
29
+ import org.json.JSONObject;
30
+ import android.location.Location;
31
+ import com.getcapacitor.Logger;
30
32
 
31
33
  @CapacitorPlugin(
32
34
  name = "CameraPreview",
@@ -39,6 +41,10 @@ import com.ahm.capacitor.camera.preview.model.LensInfo;
39
41
  strings = { CAMERA },
40
42
  alias = CameraPreview.CAMERA_ONLY_PERMISSION_ALIAS
41
43
  ),
44
+ @Permission(
45
+ strings = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION },
46
+ alias = CameraPreview.CAMERA_WITH_LOCATION_PERMISSION_ALIAS
47
+ )
42
48
  }
43
49
  )
44
50
  public class CameraPreview
@@ -47,12 +53,15 @@ public class CameraPreview
47
53
 
48
54
  static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
49
55
  static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
56
+ static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS = "cameraWithLocation";
50
57
 
51
58
  private String captureCallbackId = "";
52
59
  private String snapshotCallbackId = "";
53
60
  private String cameraStartCallbackId = "";
54
61
  private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
55
62
  private CameraXView cameraXView;
63
+ private FusedLocationProviderClient fusedLocationClient;
64
+ private Location lastLocation;
56
65
 
57
66
  @PluginMethod
58
67
  public void start(PluginCall call) {
@@ -83,15 +92,62 @@ public class CameraPreview
83
92
  }
84
93
 
85
94
  @PluginMethod
86
- public void capture(PluginCall call) {
95
+ public void capture(final PluginCall call) {
87
96
  if (cameraXView == null || !cameraXView.isRunning()) {
88
97
  call.reject("Camera is not running");
89
98
  return;
90
99
  }
100
+
101
+ final boolean withExifLocation = call.getBoolean("withExifLocation", false);
102
+
103
+ if (withExifLocation) {
104
+ if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) != PermissionState.GRANTED) {
105
+ requestPermissionForAlias(CAMERA_WITH_LOCATION_PERMISSION_ALIAS, call, "captureWithLocationPermission");
106
+ } else {
107
+ getLocationAndCapture(call);
108
+ }
109
+ } else {
110
+ captureWithoutLocation(call);
111
+ }
112
+ }
113
+
114
+ @PermissionCallback
115
+ private void captureWithLocationPermission(PluginCall call) {
116
+ if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) == PermissionState.GRANTED) {
117
+ getLocationAndCapture(call);
118
+ } else {
119
+ Logger.warn("Location permission denied. Capturing photo without location data.");
120
+ captureWithoutLocation(call);
121
+ }
122
+ }
123
+
124
+ private void getLocationAndCapture(PluginCall call) {
125
+ if (fusedLocationClient == null) {
126
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(getContext());
127
+ }
128
+ fusedLocationClient.getLastLocation().addOnSuccessListener(getActivity(), location -> {
129
+ lastLocation = location;
130
+ proceedWithCapture(call, lastLocation);
131
+ }).addOnFailureListener(e -> {
132
+ Logger.error("Failed to get location: " + e.getMessage());
133
+ proceedWithCapture(call, null);
134
+ });
135
+ }
136
+
137
+ private void captureWithoutLocation(PluginCall call) {
138
+ proceedWithCapture(call, null);
139
+ }
140
+
141
+ private void proceedWithCapture(PluginCall call, Location location) {
91
142
  bridge.saveCall(call);
92
143
  captureCallbackId = call.getCallbackId();
144
+
93
145
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
94
- cameraXView.capturePhoto(quality);
146
+ final boolean saveToGallery = call.getBoolean("saveToGallery", false);
147
+ Integer width = call.getInt("width");
148
+ Integer height = call.getInt("height");
149
+
150
+ cameraXView.capturePhoto(quality, saveToGallery, width, height, location);
95
151
  }
96
152
 
97
153
  @PluginMethod
@@ -326,13 +382,13 @@ public class CameraPreview
326
382
  final int width = call.getInt("width", 0);
327
383
  final int height = call.getInt("height", 0);
328
384
  final int paddingBottom = call.getInt("paddingBottom", 0);
329
- final boolean toBack = call.getBoolean("toBack", true);
330
- final boolean storeToFile = call.getBoolean("storeToFile", false);
331
- final boolean enableOpacity = call.getBoolean("enableOpacity", false);
332
- final boolean enableZoom = call.getBoolean("enableZoom", false);
333
- final boolean disableExifHeaderStripping = call.getBoolean("disableExifHeaderStripping", false);
334
- final boolean lockOrientation = call.getBoolean("lockAndroidOrientation", false);
335
- final boolean disableAudio = call.getBoolean("disableAudio", true);
385
+ final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
386
+ final boolean storeToFile = Boolean.TRUE.equals(call.getBoolean("storeToFile", false));
387
+ final boolean enableOpacity = Boolean.TRUE.equals(call.getBoolean("enableOpacity", false));
388
+ final boolean enableZoom = Boolean.TRUE.equals(call.getBoolean("enableZoom", false));
389
+ final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
390
+ final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
391
+ final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
336
392
 
337
393
  float targetZoom = 1.0f;
338
394
  // Check if the selected device is a physical ultra-wide
@@ -367,8 +423,8 @@ public class CameraPreview
367
423
  }
368
424
  int computedX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, x, metrics);
369
425
  int computedY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, y, metrics);
370
- int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : (int) getBridge().getWebView().getWidth();
371
- int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : (int) getBridge().getWebView().getHeight();
426
+ int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : getBridge().getWebView().getWidth();
427
+ int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : getBridge().getWebView().getHeight();
372
428
  computedHeight -= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, metrics);
373
429
 
374
430
  CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
@@ -382,15 +438,28 @@ public class CameraPreview
382
438
  }
383
439
 
384
440
  @Override
385
- public void onPictureTaken(String result) {
386
- JSObject jsObject = new JSObject();
387
- jsObject.put("value", result);
388
- bridge.getSavedCall(captureCallbackId).resolve(jsObject);
441
+ public void onPictureTaken(String base64, JSONObject exif) {
442
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
443
+ if (pluginCall == null) {
444
+ Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
445
+ return;
446
+ }
447
+ JSObject result = new JSObject();
448
+ result.put("value", base64);
449
+ result.put("exif", exif);
450
+ pluginCall.resolve(result);
451
+ bridge.releaseCall(pluginCall);
389
452
  }
390
453
 
391
454
  @Override
392
455
  public void onPictureTakenError(String message) {
393
- bridge.getSavedCall(captureCallbackId).reject(message);
456
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
457
+ if (pluginCall == null) {
458
+ Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
459
+ return;
460
+ }
461
+ pluginCall.reject(message);
462
+ bridge.releaseCall(pluginCall);
394
463
  }
395
464
 
396
465
  @Override
@@ -422,4 +491,5 @@ public class CameraPreview
422
491
  bridge.releaseCall(pluginCall);
423
492
  }
424
493
  }
494
+
425
495
  }