@capgo/camera-preview 7.14.8 → 7.16.1
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 +13 -1
- package/android/build.gradle +3 -2
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/CameraPreview.java +74 -47
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/CameraXView.java +586 -628
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/GridOverlayView.java +3 -2
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/model/CameraDevice.java +1 -1
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/model/CameraSessionConfiguration.java +1 -12
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/model/LensInfo.java +1 -1
- package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/model/ZoomFactors.java +1 -1
- package/android/src/main/res/layout/camera_activity.xml +1 -1
- package/dist/docs.json +1 -17
- package/dist/esm/definitions.d.ts +3 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +0 -22
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1 -5
- package/package.json +1 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +0 -79
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 |
|
package/android/build.gradle
CHANGED
|
@@ -18,7 +18,7 @@ buildscript {
|
|
|
18
18
|
apply plugin: 'com.android.library'
|
|
19
19
|
|
|
20
20
|
android {
|
|
21
|
-
namespace "
|
|
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
|
|
@@ -51,6 +51,8 @@ dependencies {
|
|
|
51
51
|
implementation 'androidx.exifinterface:exifinterface:1.4.1'
|
|
52
52
|
implementation 'com.google.android.gms:play-services-location:21.3.0'
|
|
53
53
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
|
|
54
|
+
// In-memory EXIF writing (no temp files)
|
|
55
|
+
implementation 'org.apache.commons:commons-imaging:1.0.0-alpha6'
|
|
54
56
|
|
|
55
57
|
// CameraX dependencies
|
|
56
58
|
def camerax_version = "1.5.0-rc01"
|
|
@@ -64,4 +66,3 @@ dependencies {
|
|
|
64
66
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
65
67
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
66
68
|
}
|
|
67
|
-
|
package/android/src/main/java/{com/ahm → app/capgo}/capacitor/camera/preview/CameraPreview.java
RENAMED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
package
|
|
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
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
1715
|
-
|
|
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
|
-
|
|
1721
|
-
|
|
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(
|
|
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
|
}
|