@capgo/camera-preview 7.14.8 → 7.15.0

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
@@ -84,6 +84,16 @@ Video and photo taken with the plugin are never removed, so do not forget to rem
84
84
  use https://capacitorjs.com/docs/apis/filesystem#deletefile for that
85
85
 
86
86
 
87
+ ## Touch & Gestures (JS‑Driven)
88
+
89
+ - Native tap‑to‑focus and pinch‑to‑zoom gesture handling are intentionally disabled on both Android and iOS.
90
+ - Handle all user interactions in your HTML/JS layer, then call the plugin methods:
91
+ - Focus: `await CameraPreview.setFocus({ x, y })` with normalized coordinates (0–1) relative to the preview bounds.
92
+ - Zoom: `await CameraPreview.setZoom({ level })` using your own gesture/UI logic.
93
+ - Rationale: native gesture recognizers can conflict with your HTML touch handlers and block UI interactions. Keeping gestures in JS guarantees your UI receives touches as intended.
94
+ - The `enableZoom` start option is removed. Use JS gestures + `setZoom(...)`.
95
+ - Tip: When using `toBack: true`, the WebView is in front of the camera and receives touches; design your overlay UI there and drive focus/zoom through the JS API.
96
+
87
97
  ## Fast base64 from file path (no bridge)
88
98
 
89
99
  When using `storeToFile: true`, you can avoid sending large base64 strings over the Capacitor bridge:
@@ -784,6 +794,9 @@ setFocus(options: { x: number; y: number; }) => Promise<void>
784
794
 
785
795
  Sets the camera focus to a specific point in the preview.
786
796
 
797
+ Note: The plugin does not attach any native tap-to-focus gesture handlers. Handle taps in
798
+ your HTML/JS (e.g., on the overlaying UI), then pass normalized coordinates here.
799
+
787
800
  | Param | Type | Description |
788
801
  | ------------- | -------------------------------------- | -------------------- |
789
802
  | **`options`** | <code>{ x: number; y: number; }</code> | - The focus options. |
@@ -999,7 +1012,6 @@ Defines the configuration options for starting the camera preview.
999
1012
  | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> | |
1000
1013
  | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> | |
1001
1014
  | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> | |
1002
- | **`enableZoom`** | <code>boolean</code> | If true, enables pinch-to-zoom functionality on the preview. | <code>false</code> | |
1003
1015
  | **`disableFocusIndicator`** | <code>boolean</code> | If true, disables the visual focus indicator when tapping to focus. | <code>false</code> | |
1004
1016
  | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | | |
1005
1017
  | **`initialZoomLevel`** | <code>number</code> | The initial zoom level when starting the camera preview. If the requested zoom level is not available, the native plugin will reject. | <code>1.0</code> | 2.2.0 |
@@ -18,7 +18,7 @@ buildscript {
18
18
  apply plugin: 'com.android.library'
19
19
 
20
20
  android {
21
- namespace "com.ahm.capacitor.camera.preview.capacitorcamerapreview"
21
+ namespace "app.capgo.capacitor.camera.preview.capacitorcamerapreview"
22
22
  compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
23
23
  defaultConfig {
24
24
  minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
@@ -1,15 +1,18 @@
1
- package com.ahm.capacitor.camera.preview;
1
+ package app.capgo.capacitor.camera.preview;
2
2
 
3
3
  import static android.Manifest.permission.CAMERA;
4
4
  import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
6
  import android.Manifest;
7
+ import android.annotation.SuppressLint;
7
8
  import android.content.pm.ActivityInfo;
9
+ import android.content.pm.PackageManager;
8
10
  import android.content.res.Configuration;
9
11
  import android.graphics.Color;
10
12
  import android.graphics.drawable.ColorDrawable;
11
13
  import android.graphics.drawable.Drawable;
12
14
  import android.location.Location;
15
+ import android.net.Uri;
13
16
  import android.util.DisplayMetrics;
14
17
  import android.util.Log;
15
18
  import android.util.Size;
@@ -17,13 +20,15 @@ import android.view.OrientationEventListener;
17
20
  import android.view.View;
18
21
  import android.view.ViewGroup;
19
22
  import android.webkit.WebView;
23
+ import androidx.annotation.RequiresPermission;
24
+ import androidx.core.app.ActivityCompat;
20
25
  import androidx.core.graphics.Insets;
21
26
  import androidx.core.view.ViewCompat;
22
27
  import androidx.core.view.WindowInsetsCompat;
23
- import com.ahm.capacitor.camera.preview.model.CameraDevice;
24
- import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
25
- import com.ahm.capacitor.camera.preview.model.LensInfo;
26
- import com.ahm.capacitor.camera.preview.model.ZoomFactors;
28
+ import app.capgo.capacitor.camera.preview.model.CameraDevice;
29
+ import app.capgo.capacitor.camera.preview.model.CameraSessionConfiguration;
30
+ import app.capgo.capacitor.camera.preview.model.LensInfo;
31
+ import app.capgo.capacitor.camera.preview.model.ZoomFactors;
27
32
  import com.getcapacitor.JSArray;
28
33
  import com.getcapacitor.JSObject;
29
34
  import com.getcapacitor.Logger;
@@ -107,7 +112,7 @@ public class CameraPreview
107
112
  "cameraWithLocation";
108
113
 
109
114
  private String captureCallbackId = "";
110
- private String snapshotCallbackId = "";
115
+ private String sampleCallbackId = "";
111
116
  private String cameraStartCallbackId = "";
112
117
  private int previousOrientationRequest =
113
118
  ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -266,6 +271,7 @@ public class CameraPreview
266
271
  call.resolve();
267
272
  }
268
273
 
274
+ @SuppressLint("MissingPermission")
269
275
  @PluginMethod
270
276
  public void capture(final PluginCall call) {
271
277
  if (cameraXView == null || !cameraXView.isRunning()) {
@@ -273,7 +279,9 @@ public class CameraPreview
273
279
  return;
274
280
  }
275
281
 
276
- final boolean withExifLocation = call.getBoolean("withExifLocation", false);
282
+ final boolean withExifLocation = Boolean.TRUE.equals(
283
+ call.getBoolean("withExifLocation", false)
284
+ );
277
285
 
278
286
  if (withExifLocation) {
279
287
  if (
@@ -293,12 +301,27 @@ public class CameraPreview
293
301
  }
294
302
  }
295
303
 
304
+ @SuppressLint("MissingPermission")
296
305
  @PermissionCallback
297
306
  private void captureWithLocationPermission(PluginCall call) {
298
307
  if (
299
308
  getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) ==
300
309
  PermissionState.GRANTED
301
310
  ) {
311
+ if (
312
+ ActivityCompat.checkSelfPermission(
313
+ getContext(),
314
+ Manifest.permission.ACCESS_FINE_LOCATION
315
+ ) !=
316
+ PackageManager.PERMISSION_GRANTED ||
317
+ ActivityCompat.checkSelfPermission(
318
+ getContext(),
319
+ Manifest.permission.ACCESS_COARSE_LOCATION
320
+ ) !=
321
+ PackageManager.PERMISSION_GRANTED
322
+ ) {
323
+ return;
324
+ }
302
325
  getLocationAndCapture(call);
303
326
  } else {
304
327
  Logger.warn(
@@ -308,6 +331,12 @@ public class CameraPreview
308
331
  }
309
332
  }
310
333
 
334
+ @RequiresPermission(
335
+ allOf = {
336
+ Manifest.permission.ACCESS_FINE_LOCATION,
337
+ Manifest.permission.ACCESS_COARSE_LOCATION,
338
+ }
339
+ )
311
340
  private void getLocationAndCapture(PluginCall call) {
312
341
  if (fusedLocationClient == null) {
313
342
  fusedLocationClient = LocationServices.getFusedLocationProviderClient(
@@ -335,7 +364,9 @@ public class CameraPreview
335
364
  captureCallbackId = call.getCallbackId();
336
365
 
337
366
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
338
- final boolean saveToGallery = call.getBoolean("saveToGallery", false);
367
+ final boolean saveToGallery = Boolean.TRUE.equals(
368
+ call.getBoolean("saveToGallery")
369
+ );
339
370
  Integer width = call.getInt("width");
340
371
  Integer height = call.getInt("height");
341
372
 
@@ -349,7 +380,7 @@ public class CameraPreview
349
380
  return;
350
381
  }
351
382
  bridge.saveCall(call);
352
- snapshotCallbackId = call.getCallbackId();
383
+ sampleCallbackId = call.getCallbackId();
353
384
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
354
385
  cameraXView.captureSample(quality);
355
386
  }
@@ -442,7 +473,7 @@ public class CameraPreview
442
473
  deviceJson.put("label", device.getLabel());
443
474
  deviceJson.put("position", device.getPosition());
444
475
  JSArray lensesArray = new JSArray();
445
- for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
476
+ for (app.capgo.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
446
477
  JSObject lensJson = new JSObject();
447
478
  lensJson.put("focalLength", lens.getFocalLength());
448
479
  lensJson.put("deviceType", lens.getDeviceType());
@@ -496,7 +527,7 @@ public class CameraPreview
496
527
  float minUltra = 0.5f;
497
528
 
498
529
  for (CameraDevice device : devices) {
499
- for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
530
+ for (app.capgo.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
500
531
  if ("ultraWide".equals(lens.getDeviceType())) {
501
532
  hasUltraWide = true;
502
533
  // Use overall minZoom for that device as the button value to represent UW
@@ -629,6 +660,7 @@ public class CameraPreview
629
660
  return;
630
661
  }
631
662
  Float opacity = call.getFloat("opacity", 1.0f);
663
+ //noinspection DataFlowIssue
632
664
  cameraXView.setOpacity(opacity);
633
665
  call.resolve();
634
666
  }
@@ -724,8 +756,11 @@ public class CameraPreview
724
756
  (y == -1) +
725
757
  ")"
726
758
  );
759
+ //noinspection DataFlowIssue
727
760
  final int width = call.getInt("width", 0);
761
+ //noinspection DataFlowIssue
728
762
  final int height = call.getInt("height", 0);
763
+ //noinspection DataFlowIssue
729
764
  final int paddingBottom = call.getInt("paddingBottom", 0);
730
765
  final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
731
766
  final boolean storeToFile = Boolean.TRUE.equals(
@@ -734,9 +769,6 @@ public class CameraPreview
734
769
  final boolean enableOpacity = Boolean.TRUE.equals(
735
770
  call.getBoolean("enableOpacity", false)
736
771
  );
737
- final boolean enableZoom = Boolean.TRUE.equals(
738
- call.getBoolean("enableZoom", false)
739
- );
740
772
  final boolean disableExifHeaderStripping = Boolean.TRUE.equals(
741
773
  call.getBoolean("disableExifHeaderStripping", false)
742
774
  );
@@ -750,7 +782,9 @@ public class CameraPreview
750
782
  final String aspectRatio = call.getString("aspectRatio", "4:3");
751
783
  final String gridMode = call.getString("gridMode", "none");
752
784
  final String positioning = call.getString("positioning", "top");
785
+ //noinspection DataFlowIssue
753
786
  final float initialZoomLevel = call.getFloat("initialZoomLevel", 1.0f);
787
+ //noinspection DataFlowIssue
754
788
  final boolean disableFocusIndicator = call.getBoolean(
755
789
  "disableFocusIndicator",
756
790
  false
@@ -1014,7 +1048,7 @@ public class CameraPreview
1014
1048
  // Position vertically based on positioning parameter
1015
1049
  int screenHeight = metrics.heightPixels;
1016
1050
 
1017
- switch (positioning) {
1051
+ switch (Objects.requireNonNull(positioning)) {
1018
1052
  case "top":
1019
1053
  computedY = 0;
1020
1054
  Log.d("CameraPreview", "Positioning at top: computedY=0");
@@ -1181,7 +1215,6 @@ public class CameraPreview
1181
1215
  toBack,
1182
1216
  storeToFile,
1183
1217
  enableOpacity,
1184
- enableZoom,
1185
1218
  disableExifHeaderStripping,
1186
1219
  disableAudio,
1187
1220
  1.0f,
@@ -1711,14 +1744,31 @@ public class CameraPreview
1711
1744
 
1712
1745
  @Override
1713
1746
  public void onSampleTaken(String result) {
1714
- // Handle sample taken if needed
1715
- Log.i("CameraPreview", "Sample taken: " + result);
1747
+ PluginCall call = bridge.getSavedCall(sampleCallbackId);
1748
+ if (call != null) {
1749
+ JSObject ret = new JSObject();
1750
+ ret.put("value", result);
1751
+ call.resolve(ret);
1752
+ bridge.releaseCall(call);
1753
+ sampleCallbackId = null;
1754
+ } else {
1755
+ Log.w("CameraPreview", "onSampleTaken: no pending call to resolve");
1756
+ }
1716
1757
  }
1717
1758
 
1718
1759
  @Override
1719
1760
  public void onSampleTakenError(String message) {
1720
- // Handle sample taken error if needed
1721
- Log.e("CameraPreview", "Sample taken error: " + message);
1761
+ PluginCall call = bridge.getSavedCall(sampleCallbackId);
1762
+ if (call != null) {
1763
+ call.reject(message);
1764
+ bridge.releaseCall(call);
1765
+ sampleCallbackId = null;
1766
+ } else {
1767
+ Log.e(
1768
+ "CameraPreview",
1769
+ "Sample taken error (no pending call): " + message
1770
+ );
1771
+ }
1722
1772
  }
1723
1773
 
1724
1774
  @Override
@@ -1897,7 +1947,9 @@ public class CameraPreview
1897
1947
  return;
1898
1948
  }
1899
1949
  try {
1900
- java.io.File f = new java.io.File(android.net.Uri.parse(path).getPath());
1950
+ java.io.File f = new java.io.File(
1951
+ Objects.requireNonNull(Uri.parse(path).getPath())
1952
+ );
1901
1953
  boolean deleted = f.exists() && f.delete();
1902
1954
  JSObject ret = new JSObject();
1903
1955
  ret.put("success", deleted);
@@ -2031,20 +2083,9 @@ public class CameraPreview
2031
2083
  if (orientation == Configuration.ORIENTATION_PORTRAIT) {
2032
2084
  // Portrait: return top inset (notch/status bar)
2033
2085
  notchInsetPx = Math.max(cutout.top, sysBars.top);
2034
-
2035
- // If no cutout detected but we have system bars, use status bar height as fallback
2036
- if (cutout.top == 0 && sysBars.top > 0) {
2037
- notchInsetPx = sysBars.top;
2038
- }
2039
2086
  } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
2040
2087
  // Landscape: return left inset (notch moved to side)
2041
2088
  notchInsetPx = Math.max(cutout.left, sysBars.left);
2042
-
2043
- // If no cutout detected but we have system bars, use left system bar as fallback
2044
- if (cutout.left == 0 && sysBars.left > 0) {
2045
- notchInsetPx = sysBars.left;
2046
- }
2047
-
2048
2089
  // Additional fallback: some devices might have the notch on the right in landscape
2049
2090
  // If left is 0, check right side as well
2050
2091
  if (notchInsetPx == 0) {
@@ -2070,12 +2111,9 @@ public class CameraPreview
2070
2111
  call.resolve(ret);
2071
2112
  }
2072
2113
 
2073
- private boolean approxEqualPx(int a, int b) {
2074
- return Math.abs(a - b) <= 2; // within 2px tolerance
2075
- }
2076
-
2077
2114
  private int getStatusBarHeightPx() {
2078
2115
  int result = 0;
2116
+ @SuppressLint("InternalInsetResource")
2079
2117
  int resourceId = getContext()
2080
2118
  .getResources()
2081
2119
  .getIdentifier("status_bar_height", "dimen", "android");
@@ -2084,15 +2122,4 @@ public class CameraPreview
2084
2122
  }
2085
2123
  return result;
2086
2124
  }
2087
-
2088
- private int getNavigationBarHeightPx() {
2089
- int result = 0;
2090
- int resourceId = getContext()
2091
- .getResources()
2092
- .getIdentifier("navigation_bar_height", "dimen", "android");
2093
- if (resourceId > 0) {
2094
- result = getContext().getResources().getDimensionPixelSize(resourceId);
2095
- }
2096
- return result;
2097
- }
2098
2125
  }