@capgo/camera-preview 7.4.0-alpha.1 → 7.4.0-alpha.13
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 +59 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +66 -2
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +89 -205
- package/dist/docs.json +49 -0
- package/dist/esm/definitions.d.ts +15 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +24 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +8 -0
- package/dist/esm/web.js +18 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +41 -5
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +41 -5
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +63 -23
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +282 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Take into account that this will make transparent all ion-content on application
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
If the camera preview is not displaying after applying the above styles, apply transparent background color to the root div element of the parent component
|
|
40
|
-
Ex: VueJS >> App.vue component
|
|
40
|
+
Ex: VueJS >> App.vue component
|
|
41
41
|
```html
|
|
42
42
|
<template>
|
|
43
43
|
<ion-app id="app">
|
|
@@ -76,6 +76,24 @@ Video and photo taken with the plugin are never removed, so do not forget to rem
|
|
|
76
76
|
use https://capacitorjs.com/docs/apis/filesystem#deletefile for that
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
## Fast base64 from file path (no bridge)
|
|
80
|
+
|
|
81
|
+
When using `storeToFile: true`, you can avoid sending large base64 strings over the Capacitor bridge:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { CameraPreview, getBase64FromFilePath } from '@capgo/camera-preview'
|
|
85
|
+
|
|
86
|
+
// Take a picture and get a file path
|
|
87
|
+
const { value: filePath } = await CameraPreview.capture({ quality: 85, storeToFile: true })
|
|
88
|
+
|
|
89
|
+
// Convert the file to base64 entirely on the JS side (fast, no bridge)
|
|
90
|
+
const base64 = await getBase64FromFilePath(filePath)
|
|
91
|
+
|
|
92
|
+
// Optionally cleanup the temp file natively
|
|
93
|
+
await CameraPreview.deleteFile({ path: filePath })
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
79
97
|
# Installation
|
|
80
98
|
|
|
81
99
|
```
|
|
@@ -234,6 +252,7 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
|
|
|
234
252
|
* [`isRunning()`](#isrunning)
|
|
235
253
|
* [`getAvailableDevices()`](#getavailabledevices)
|
|
236
254
|
* [`getZoom()`](#getzoom)
|
|
255
|
+
* [`getZoomButtonValues()`](#getzoombuttonvalues)
|
|
237
256
|
* [`setZoom(...)`](#setzoom)
|
|
238
257
|
* [`getFlashMode()`](#getflashmode)
|
|
239
258
|
* [`removeAllListeners()`](#removealllisteners)
|
|
@@ -242,6 +261,7 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
|
|
|
242
261
|
* [`getPreviewSize()`](#getpreviewsize)
|
|
243
262
|
* [`setPreviewSize(...)`](#setpreviewsize)
|
|
244
263
|
* [`setFocus(...)`](#setfocus)
|
|
264
|
+
* [`addListener('screenResize', ...)`](#addlistenerscreenresize-)
|
|
245
265
|
* [Interfaces](#interfaces)
|
|
246
266
|
* [Type Aliases](#type-aliases)
|
|
247
267
|
* [Enums](#enums)
|
|
@@ -559,6 +579,21 @@ Gets the current zoom state, including min/max and current lens info.
|
|
|
559
579
|
--------------------
|
|
560
580
|
|
|
561
581
|
|
|
582
|
+
### getZoomButtonValues()
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
getZoomButtonValues() => Promise<{ values: number[]; }>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Returns zoom button values for quick switching.
|
|
589
|
+
- iOS/Android: includes 0.5 if ultra-wide available; 1 and 2 if wide available; 3 if telephoto available
|
|
590
|
+
- Web: unsupported
|
|
591
|
+
|
|
592
|
+
**Returns:** <code>Promise<{ values: number[]; }></code>
|
|
593
|
+
|
|
594
|
+
--------------------
|
|
595
|
+
|
|
596
|
+
|
|
562
597
|
### setZoom(...)
|
|
563
598
|
|
|
564
599
|
```typescript
|
|
@@ -683,6 +718,22 @@ Sets the camera focus to a specific point in the preview.
|
|
|
683
718
|
--------------------
|
|
684
719
|
|
|
685
720
|
|
|
721
|
+
### addListener('screenResize', ...)
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
addListener(eventName: "screenResize", listenerFunc: (data: { width: number; height: number; x: number; y: number; }) => void) => Promise<PluginListenerHandle>
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
| Param | Type |
|
|
728
|
+
| ------------------ | ---------------------------------------------------------------------------------------- |
|
|
729
|
+
| **`eventName`** | <code>'screenResize'</code> |
|
|
730
|
+
| **`listenerFunc`** | <code>(data: { width: number; height: number; x: number; y: number; }) => void</code> |
|
|
731
|
+
|
|
732
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
733
|
+
|
|
734
|
+
--------------------
|
|
735
|
+
|
|
736
|
+
|
|
686
737
|
### Interfaces
|
|
687
738
|
|
|
688
739
|
|
|
@@ -816,6 +867,13 @@ Represents the detailed information of the currently active lens.
|
|
|
816
867
|
| **`digitalZoom`** | <code>number</code> | The current digital zoom factor applied on top of the base zoom. |
|
|
817
868
|
|
|
818
869
|
|
|
870
|
+
#### PluginListenerHandle
|
|
871
|
+
|
|
872
|
+
| Prop | Type |
|
|
873
|
+
| ------------ | ----------------------------------------- |
|
|
874
|
+
| **`remove`** | <code>() => Promise<void></code> |
|
|
875
|
+
|
|
876
|
+
|
|
819
877
|
### Type Aliases
|
|
820
878
|
|
|
821
879
|
|
|
@@ -276,6 +276,53 @@ public class CameraPreview
|
|
|
276
276
|
call.resolve(result);
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
@PluginMethod
|
|
280
|
+
public void getZoomButtonValues(PluginCall call) {
|
|
281
|
+
// Build a sorted set to dedupe and order ascending
|
|
282
|
+
java.util.Set<Double> sorted = new java.util.TreeSet<>();
|
|
283
|
+
sorted.add(1.0);
|
|
284
|
+
sorted.add(2.0);
|
|
285
|
+
|
|
286
|
+
// Try to detect ultra-wide to include its min zoom (often 0.5)
|
|
287
|
+
try {
|
|
288
|
+
List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
|
|
289
|
+
getContext()
|
|
290
|
+
);
|
|
291
|
+
ZoomFactors zoomFactors = cameraXView.getZoomFactors();
|
|
292
|
+
boolean hasUltraWide = false;
|
|
293
|
+
boolean hasTelephoto = false;
|
|
294
|
+
float minUltra = 0.5f;
|
|
295
|
+
|
|
296
|
+
for (CameraDevice device : devices) {
|
|
297
|
+
for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
|
|
298
|
+
if ("ultraWide".equals(lens.getDeviceType())) {
|
|
299
|
+
hasUltraWide = true;
|
|
300
|
+
// Use overall minZoom for that device as the button value to represent UW
|
|
301
|
+
minUltra = Math.max(minUltra, zoomFactors.getMin());
|
|
302
|
+
} else if ("telephoto".equals(lens.getDeviceType())) {
|
|
303
|
+
hasTelephoto = true;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (hasUltraWide) {
|
|
308
|
+
sorted.add((double) minUltra);
|
|
309
|
+
}
|
|
310
|
+
if (hasTelephoto) {
|
|
311
|
+
sorted.add(3.0);
|
|
312
|
+
}
|
|
313
|
+
} catch (Exception ignored) {
|
|
314
|
+
// Ignore and keep defaults
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
JSObject result = new JSObject();
|
|
318
|
+
JSArray values = new JSArray();
|
|
319
|
+
for (Double v : sorted) {
|
|
320
|
+
values.put(v);
|
|
321
|
+
}
|
|
322
|
+
result.put("values", values);
|
|
323
|
+
call.resolve(result);
|
|
324
|
+
}
|
|
325
|
+
|
|
279
326
|
@PluginMethod
|
|
280
327
|
public void setZoom(PluginCall call) {
|
|
281
328
|
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
@@ -287,9 +334,8 @@ public class CameraPreview
|
|
|
287
334
|
call.reject("level parameter is required");
|
|
288
335
|
return;
|
|
289
336
|
}
|
|
290
|
-
Boolean autoFocus = call.getBoolean("autoFocus", true);
|
|
291
337
|
try {
|
|
292
|
-
cameraXView.setZoom(level
|
|
338
|
+
cameraXView.setZoom(level);
|
|
293
339
|
call.resolve();
|
|
294
340
|
} catch (Exception e) {
|
|
295
341
|
call.reject("Failed to set zoom: " + e.getMessage());
|
|
@@ -1191,4 +1237,22 @@ public class CameraPreview
|
|
|
1191
1237
|
call.resolve(ret);
|
|
1192
1238
|
});
|
|
1193
1239
|
}
|
|
1240
|
+
|
|
1241
|
+
@PluginMethod
|
|
1242
|
+
public void deleteFile(PluginCall call) {
|
|
1243
|
+
String path = call.getString("path");
|
|
1244
|
+
if (path == null || path.isEmpty()) {
|
|
1245
|
+
call.reject("path parameter is required");
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
try {
|
|
1249
|
+
java.io.File f = new java.io.File(android.net.Uri.parse(path).getPath());
|
|
1250
|
+
boolean deleted = f.exists() && f.delete();
|
|
1251
|
+
JSObject ret = new JSObject();
|
|
1252
|
+
ret.put("success", deleted);
|
|
1253
|
+
call.resolve(ret);
|
|
1254
|
+
} catch (Exception e) {
|
|
1255
|
+
call.reject("Failed to delete file: " + e.getMessage());
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1194
1258
|
}
|
|
@@ -102,6 +102,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
102
102
|
private GridOverlayView gridOverlayView;
|
|
103
103
|
private FrameLayout previewContainer;
|
|
104
104
|
private View focusIndicatorView;
|
|
105
|
+
private long focusIndicatorAnimationId = 0; // Incrementing token to invalidate previous animations
|
|
105
106
|
private CameraSelector currentCameraSelector;
|
|
106
107
|
private String currentDeviceId;
|
|
107
108
|
private int currentFlashMode = ImageCapture.FLASH_MODE_OFF;
|
|
@@ -726,7 +727,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
726
727
|
}
|
|
727
728
|
}
|
|
728
729
|
|
|
729
|
-
|
|
730
|
+
setZoom(initialZoom);
|
|
730
731
|
}
|
|
731
732
|
|
|
732
733
|
isRunning = true;
|
|
@@ -1040,12 +1041,40 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1040
1041
|
saveImageToGallery(bytes);
|
|
1041
1042
|
}
|
|
1042
1043
|
|
|
1043
|
-
String
|
|
1044
|
+
String resultValue;
|
|
1045
|
+
boolean returnFileUri =
|
|
1046
|
+
sessionConfig != null && sessionConfig.isStoreToFile();
|
|
1047
|
+
if (returnFileUri) {
|
|
1048
|
+
// Persist processed image to a file and return its URI to avoid heavy base64 bridging
|
|
1049
|
+
try {
|
|
1050
|
+
String fileName =
|
|
1051
|
+
"cpcp_" +
|
|
1052
|
+
new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(
|
|
1053
|
+
new java.util.Date()
|
|
1054
|
+
) +
|
|
1055
|
+
".jpg";
|
|
1056
|
+
File outDir = context.getCacheDir();
|
|
1057
|
+
File outFile = new File(outDir, fileName);
|
|
1058
|
+
FileOutputStream outFos = new FileOutputStream(outFile);
|
|
1059
|
+
outFos.write(bytes);
|
|
1060
|
+
outFos.close();
|
|
1061
|
+
|
|
1062
|
+
// Return a file path; apps can convert via Capacitor.convertFileSrc on JS side
|
|
1063
|
+
resultValue = outFile.getAbsolutePath();
|
|
1064
|
+
} catch (IOException ioEx) {
|
|
1065
|
+
Log.e(TAG, "capturePhoto: Failed to write image file", ioEx);
|
|
1066
|
+
// Fallback to base64 if file write fails
|
|
1067
|
+
resultValue = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
// Backward-compatible behavior
|
|
1071
|
+
resultValue = Base64.encodeToString(bytes, Base64.NO_WRAP);
|
|
1072
|
+
}
|
|
1044
1073
|
|
|
1045
1074
|
tempFile.delete();
|
|
1046
1075
|
|
|
1047
1076
|
if (listener != null) {
|
|
1048
|
-
listener.onPictureTaken(
|
|
1077
|
+
listener.onPictureTaken(resultValue, exifData);
|
|
1049
1078
|
}
|
|
1050
1079
|
} catch (Exception e) {
|
|
1051
1080
|
Log.e(TAG, "capturePhoto: Error processing image", e);
|
|
@@ -1605,7 +1634,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1605
1634
|
}
|
|
1606
1635
|
}
|
|
1607
1636
|
|
|
1608
|
-
public void setZoom(float zoomRatio
|
|
1637
|
+
public void setZoom(float zoomRatio) throws Exception {
|
|
1609
1638
|
if (camera == null) {
|
|
1610
1639
|
throw new Exception("Camera not initialized");
|
|
1611
1640
|
}
|
|
@@ -1614,26 +1643,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1614
1643
|
|
|
1615
1644
|
// Just let CameraX handle everything - it should automatically switch lenses
|
|
1616
1645
|
try {
|
|
1617
|
-
|
|
1618
|
-
.getCameraControl()
|
|
1619
|
-
.setZoomRatio(zoomRatio);
|
|
1646
|
+
ZoomFactors zoomFactors = getZoomFactors();
|
|
1620
1647
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
triggerAutoFocus();
|
|
1630
|
-
}
|
|
1631
|
-
} catch (Exception e) {
|
|
1632
|
-
Log.e(TAG, "Error setting zoom: " + e.getMessage());
|
|
1633
|
-
}
|
|
1634
|
-
},
|
|
1635
|
-
ContextCompat.getMainExecutor(context)
|
|
1636
|
-
);
|
|
1648
|
+
if (zoomRatio < zoomFactors.getMin()) {
|
|
1649
|
+
zoomRatio = zoomFactors.getMin();
|
|
1650
|
+
} else if (zoomRatio > zoomFactors.getMax()) {
|
|
1651
|
+
zoomRatio = zoomFactors.getMax();
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
camera.getCameraControl().setZoomRatio(zoomRatio);
|
|
1655
|
+
// Note: autofocus is intentionally not triggered on zoom because it's done by CameraX
|
|
1637
1656
|
} catch (Exception e) {
|
|
1638
1657
|
Log.e(TAG, "Failed to set zoom: " + e.getMessage());
|
|
1639
1658
|
throw e;
|
|
@@ -1748,8 +1767,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1748
1767
|
return;
|
|
1749
1768
|
}
|
|
1750
1769
|
|
|
1751
|
-
// Remove any existing focus indicator
|
|
1770
|
+
// Remove any existing focus indicator and cancel its animation
|
|
1752
1771
|
if (focusIndicatorView != null) {
|
|
1772
|
+
try {
|
|
1773
|
+
focusIndicatorView.clearAnimation();
|
|
1774
|
+
} catch (Exception ignore) {}
|
|
1753
1775
|
previewContainer.removeView(focusIndicatorView);
|
|
1754
1776
|
focusIndicatorView = null;
|
|
1755
1777
|
}
|
|
@@ -1783,11 +1805,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1783
1805
|
container.setBackground(drawable);
|
|
1784
1806
|
|
|
1785
1807
|
focusIndicatorView = container;
|
|
1808
|
+
// Bump animation token; everything after this must validate against this token
|
|
1809
|
+
final long thisAnimationId = ++focusIndicatorAnimationId;
|
|
1810
|
+
final View thisIndicatorView = focusIndicatorView;
|
|
1786
1811
|
|
|
1787
1812
|
// Set initial state for smooth animation
|
|
1788
1813
|
focusIndicatorView.setAlpha(1f); // Start visible
|
|
1789
|
-
focusIndicatorView.setScaleX(1.
|
|
1790
|
-
focusIndicatorView.setScaleY(1.
|
|
1814
|
+
focusIndicatorView.setScaleX(1.4f); // Start slightly larger for a quick scale-in
|
|
1815
|
+
focusIndicatorView.setScaleY(1.4f);
|
|
1791
1816
|
focusIndicatorView.setVisibility(View.VISIBLE);
|
|
1792
1817
|
|
|
1793
1818
|
// Ensure container doesn't intercept touch events
|
|
@@ -1813,16 +1838,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1813
1838
|
|
|
1814
1839
|
// Smooth scale down animation with easing (no fade needed since we start visible)
|
|
1815
1840
|
ScaleAnimation scaleAnimation = new ScaleAnimation(
|
|
1816
|
-
1.
|
|
1841
|
+
1.4f,
|
|
1817
1842
|
1.0f,
|
|
1818
|
-
1.
|
|
1843
|
+
1.4f,
|
|
1819
1844
|
1.0f,
|
|
1820
1845
|
Animation.RELATIVE_TO_SELF,
|
|
1821
1846
|
0.5f,
|
|
1822
1847
|
Animation.RELATIVE_TO_SELF,
|
|
1823
1848
|
0.5f
|
|
1824
1849
|
);
|
|
1825
|
-
scaleAnimation.setDuration(
|
|
1850
|
+
scaleAnimation.setDuration(120);
|
|
1826
1851
|
scaleAnimation.setInterpolator(
|
|
1827
1852
|
new android.view.animation.OvershootInterpolator(1.2f)
|
|
1828
1853
|
);
|
|
@@ -1830,108 +1855,50 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1830
1855
|
// Start the animation
|
|
1831
1856
|
focusIndicatorView.startAnimation(scaleAnimation);
|
|
1832
1857
|
|
|
1833
|
-
// Schedule fade out and removal
|
|
1858
|
+
// Schedule fast fade out and removal
|
|
1834
1859
|
focusIndicatorView.postDelayed(
|
|
1835
1860
|
new Runnable() {
|
|
1836
1861
|
@Override
|
|
1837
1862
|
public void run() {
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
ScaleAnimation finalScaleDown = new ScaleAnimation(
|
|
1871
|
-
1.0f,
|
|
1872
|
-
0.9f,
|
|
1873
|
-
1.0f,
|
|
1874
|
-
0.9f,
|
|
1875
|
-
Animation.RELATIVE_TO_SELF,
|
|
1876
|
-
0.5f,
|
|
1877
|
-
Animation.RELATIVE_TO_SELF,
|
|
1878
|
-
0.5f
|
|
1879
|
-
);
|
|
1880
|
-
finalScaleDown.setDuration(500);
|
|
1881
|
-
finalScaleDown.setStartOffset(300);
|
|
1882
|
-
finalScaleDown.setInterpolator(
|
|
1883
|
-
new android.view.animation.AccelerateInterpolator()
|
|
1884
|
-
);
|
|
1885
|
-
|
|
1886
|
-
finalAnimation.addAnimation(finalFadeOut);
|
|
1887
|
-
finalAnimation.addAnimation(finalScaleDown);
|
|
1888
|
-
|
|
1889
|
-
finalAnimation.setAnimationListener(
|
|
1890
|
-
new Animation.AnimationListener() {
|
|
1891
|
-
@Override
|
|
1892
|
-
public void onAnimationStart(Animation animation) {
|
|
1893
|
-
Log.d(
|
|
1894
|
-
TAG,
|
|
1895
|
-
"showFocusIndicator: Final animation started"
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
@Override
|
|
1900
|
-
public void onAnimationEnd(Animation animation) {
|
|
1901
|
-
Log.d(
|
|
1902
|
-
TAG,
|
|
1903
|
-
"showFocusIndicator: Final animation ended, removing indicator"
|
|
1904
|
-
);
|
|
1905
|
-
// Remove the focus indicator
|
|
1906
|
-
if (
|
|
1907
|
-
focusIndicatorView != null &&
|
|
1908
|
-
previewContainer != null
|
|
1909
|
-
) {
|
|
1910
|
-
previewContainer.removeView(focusIndicatorView);
|
|
1911
|
-
focusIndicatorView = null;
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
@Override
|
|
1916
|
-
public void onAnimationRepeat(Animation animation) {}
|
|
1917
|
-
}
|
|
1918
|
-
);
|
|
1919
|
-
|
|
1920
|
-
focusIndicatorView.startAnimation(finalAnimation);
|
|
1863
|
+
// Ensure this runnable belongs to the latest indicator
|
|
1864
|
+
if (
|
|
1865
|
+
focusIndicatorView != null &&
|
|
1866
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1867
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1868
|
+
) {
|
|
1869
|
+
focusIndicatorView
|
|
1870
|
+
.animate()
|
|
1871
|
+
.alpha(0f)
|
|
1872
|
+
.scaleX(0.9f)
|
|
1873
|
+
.scaleY(0.9f)
|
|
1874
|
+
.setDuration(180)
|
|
1875
|
+
.setInterpolator(
|
|
1876
|
+
new android.view.animation.AccelerateInterpolator()
|
|
1877
|
+
)
|
|
1878
|
+
.withEndAction(
|
|
1879
|
+
new Runnable() {
|
|
1880
|
+
@Override
|
|
1881
|
+
public void run() {
|
|
1882
|
+
if (
|
|
1883
|
+
focusIndicatorView != null &&
|
|
1884
|
+
previewContainer != null &&
|
|
1885
|
+
thisIndicatorView == focusIndicatorView &&
|
|
1886
|
+
thisAnimationId == focusIndicatorAnimationId
|
|
1887
|
+
) {
|
|
1888
|
+
try {
|
|
1889
|
+
focusIndicatorView.clearAnimation();
|
|
1890
|
+
} catch (Exception ignore) {}
|
|
1891
|
+
previewContainer.removeView(focusIndicatorView);
|
|
1892
|
+
focusIndicatorView = null;
|
|
1893
|
+
}
|
|
1921
1894
|
}
|
|
1922
1895
|
}
|
|
1923
|
-
|
|
1924
|
-
@Override
|
|
1925
|
-
public void onAnimationRepeat(Animation animation) {}
|
|
1926
|
-
}
|
|
1927
|
-
);
|
|
1928
|
-
|
|
1929
|
-
focusIndicatorView.startAnimation(fadeToTransparent);
|
|
1896
|
+
);
|
|
1930
1897
|
}
|
|
1931
1898
|
}
|
|
1932
1899
|
},
|
|
1933
|
-
|
|
1934
|
-
); //
|
|
1900
|
+
250
|
|
1901
|
+
); // Faster feedback
|
|
1935
1902
|
}
|
|
1936
1903
|
|
|
1937
1904
|
public static List<Size> getSupportedPictureSizes(String facing) {
|
|
@@ -1957,89 +1924,6 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1957
1924
|
return sizes;
|
|
1958
1925
|
}
|
|
1959
1926
|
|
|
1960
|
-
private void setZoomInternal(float zoomRatio) {
|
|
1961
|
-
if (camera != null) {
|
|
1962
|
-
try {
|
|
1963
|
-
float minZoom = Objects.requireNonNull(
|
|
1964
|
-
camera.getCameraInfo().getZoomState().getValue()
|
|
1965
|
-
).getMinZoomRatio();
|
|
1966
|
-
float maxZoom = camera
|
|
1967
|
-
.getCameraInfo()
|
|
1968
|
-
.getZoomState()
|
|
1969
|
-
.getValue()
|
|
1970
|
-
.getMaxZoomRatio();
|
|
1971
|
-
float currentZoom = camera
|
|
1972
|
-
.getCameraInfo()
|
|
1973
|
-
.getZoomState()
|
|
1974
|
-
.getValue()
|
|
1975
|
-
.getZoomRatio();
|
|
1976
|
-
|
|
1977
|
-
Log.d(
|
|
1978
|
-
TAG,
|
|
1979
|
-
"setZoomInternal: Current camera range: " +
|
|
1980
|
-
minZoom +
|
|
1981
|
-
"-" +
|
|
1982
|
-
maxZoom +
|
|
1983
|
-
", current: " +
|
|
1984
|
-
currentZoom
|
|
1985
|
-
);
|
|
1986
|
-
Log.d(TAG, "setZoomInternal: Requesting zoom: " + zoomRatio);
|
|
1987
|
-
|
|
1988
|
-
// Try to set zoom directly - let CameraX handle lens switching
|
|
1989
|
-
ListenableFuture<Void> zoomFuture = camera
|
|
1990
|
-
.getCameraControl()
|
|
1991
|
-
.setZoomRatio(zoomRatio);
|
|
1992
|
-
|
|
1993
|
-
zoomFuture.addListener(
|
|
1994
|
-
() -> {
|
|
1995
|
-
try {
|
|
1996
|
-
zoomFuture.get(); // Check if zoom was successful
|
|
1997
|
-
float newZoom = Objects.requireNonNull(
|
|
1998
|
-
camera.getCameraInfo().getZoomState().getValue()
|
|
1999
|
-
).getZoomRatio();
|
|
2000
|
-
Log.d(
|
|
2001
|
-
TAG,
|
|
2002
|
-
"setZoomInternal: Zoom set successfully to " +
|
|
2003
|
-
newZoom +
|
|
2004
|
-
" (requested: " +
|
|
2005
|
-
zoomRatio +
|
|
2006
|
-
")"
|
|
2007
|
-
);
|
|
2008
|
-
|
|
2009
|
-
// Check if CameraX switched cameras
|
|
2010
|
-
String newCameraId = getCameraId(camera.getCameraInfo());
|
|
2011
|
-
if (!newCameraId.equals(currentDeviceId)) {
|
|
2012
|
-
currentDeviceId = newCameraId;
|
|
2013
|
-
Log.d(
|
|
2014
|
-
TAG,
|
|
2015
|
-
"setZoomInternal: CameraX switched to camera: " + newCameraId
|
|
2016
|
-
);
|
|
2017
|
-
}
|
|
2018
|
-
} catch (Exception e) {
|
|
2019
|
-
Log.w(
|
|
2020
|
-
TAG,
|
|
2021
|
-
"setZoomInternal: Zoom operation failed: " + e.getMessage()
|
|
2022
|
-
);
|
|
2023
|
-
// Fallback: clamp to current camera's range
|
|
2024
|
-
float clampedZoom = Math.max(
|
|
2025
|
-
minZoom,
|
|
2026
|
-
Math.min(zoomRatio, maxZoom)
|
|
2027
|
-
);
|
|
2028
|
-
camera.getCameraControl().setZoomRatio(clampedZoom);
|
|
2029
|
-
Log.d(
|
|
2030
|
-
TAG,
|
|
2031
|
-
"setZoomInternal: Fallback - clamped zoom to " + clampedZoom
|
|
2032
|
-
);
|
|
2033
|
-
}
|
|
2034
|
-
},
|
|
2035
|
-
mainExecutor
|
|
2036
|
-
);
|
|
2037
|
-
} catch (Exception e) {
|
|
2038
|
-
Log.e(TAG, "setZoomInternal: Error setting zoom", e);
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
1927
|
public static List<String> getSupportedFlashModesStatic() {
|
|
2044
1928
|
try {
|
|
2045
1929
|
// For static method, we can return common flash modes
|
package/dist/docs.json
CHANGED
|
@@ -471,6 +471,16 @@
|
|
|
471
471
|
],
|
|
472
472
|
"slug": "getzoom"
|
|
473
473
|
},
|
|
474
|
+
{
|
|
475
|
+
"name": "getZoomButtonValues",
|
|
476
|
+
"signature": "() => Promise<{ values: number[]; }>",
|
|
477
|
+
"parameters": [],
|
|
478
|
+
"returns": "Promise<{ values: number[]; }>",
|
|
479
|
+
"tags": [],
|
|
480
|
+
"docs": "Returns zoom button values for quick switching.\n- iOS/Android: includes 0.5 if ultra-wide available; 1 and 2 if wide available; 3 if telephoto available\n- Web: unsupported",
|
|
481
|
+
"complexTypes": [],
|
|
482
|
+
"slug": "getzoombuttonvalues"
|
|
483
|
+
},
|
|
474
484
|
{
|
|
475
485
|
"name": "setZoom",
|
|
476
486
|
"signature": "(options: { level: number; ramp?: boolean; autoFocus?: boolean; }) => Promise<void>",
|
|
@@ -651,6 +661,29 @@
|
|
|
651
661
|
"docs": "Sets the camera focus to a specific point in the preview.",
|
|
652
662
|
"complexTypes": [],
|
|
653
663
|
"slug": "setfocus"
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
"name": "addListener",
|
|
667
|
+
"signature": "(eventName: \"screenResize\", listenerFunc: (data: { width: number; height: number; x: number; y: number; }) => void) => Promise<PluginListenerHandle>",
|
|
668
|
+
"parameters": [
|
|
669
|
+
{
|
|
670
|
+
"name": "eventName",
|
|
671
|
+
"docs": "",
|
|
672
|
+
"type": "'screenResize'"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
"name": "listenerFunc",
|
|
676
|
+
"docs": "",
|
|
677
|
+
"type": "(data: { width: number; height: number; x: number; y: number; }) => void"
|
|
678
|
+
}
|
|
679
|
+
],
|
|
680
|
+
"returns": "Promise<PluginListenerHandle>",
|
|
681
|
+
"tags": [],
|
|
682
|
+
"docs": "",
|
|
683
|
+
"complexTypes": [
|
|
684
|
+
"PluginListenerHandle"
|
|
685
|
+
],
|
|
686
|
+
"slug": "addlistenerscreenresize-"
|
|
654
687
|
}
|
|
655
688
|
],
|
|
656
689
|
"properties": []
|
|
@@ -1357,6 +1390,22 @@
|
|
|
1357
1390
|
"type": "number"
|
|
1358
1391
|
}
|
|
1359
1392
|
]
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
"name": "PluginListenerHandle",
|
|
1396
|
+
"slug": "pluginlistenerhandle",
|
|
1397
|
+
"docs": "",
|
|
1398
|
+
"tags": [],
|
|
1399
|
+
"methods": [],
|
|
1400
|
+
"properties": [
|
|
1401
|
+
{
|
|
1402
|
+
"name": "remove",
|
|
1403
|
+
"tags": [],
|
|
1404
|
+
"docs": "",
|
|
1405
|
+
"complexTypes": [],
|
|
1406
|
+
"type": "() => Promise<void>"
|
|
1407
|
+
}
|
|
1408
|
+
]
|
|
1360
1409
|
}
|
|
1361
1410
|
],
|
|
1362
1411
|
"enums": [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PluginListenerHandle } from "@capacitor/core";
|
|
1
2
|
export type CameraPosition = "rear" | "front";
|
|
2
3
|
export type FlashMode = CameraPreviewFlashMode;
|
|
3
4
|
export type GridMode = "none" | "3x3" | "4x4";
|
|
@@ -487,6 +488,14 @@ export interface CameraPreviewPlugin {
|
|
|
487
488
|
current: number;
|
|
488
489
|
lens: LensInfo;
|
|
489
490
|
}>;
|
|
491
|
+
/**
|
|
492
|
+
* Returns zoom button values for quick switching.
|
|
493
|
+
* - iOS/Android: includes 0.5 if ultra-wide available; 1 and 2 if wide available; 3 if telephoto available
|
|
494
|
+
* - Web: unsupported
|
|
495
|
+
*/
|
|
496
|
+
getZoomButtonValues(): Promise<{
|
|
497
|
+
values: number[];
|
|
498
|
+
}>;
|
|
490
499
|
/**
|
|
491
500
|
* Sets the zoom level of the camera.
|
|
492
501
|
*
|
|
@@ -572,4 +581,10 @@ export interface CameraPreviewPlugin {
|
|
|
572
581
|
x: number;
|
|
573
582
|
y: number;
|
|
574
583
|
}): Promise<void>;
|
|
584
|
+
addListener(eventName: "screenResize", listenerFunc: (data: {
|
|
585
|
+
width: number;
|
|
586
|
+
height: number;
|
|
587
|
+
x: number;
|
|
588
|
+
y: number;
|
|
589
|
+
}) => void): Promise<PluginListenerHandle>;
|
|
575
590
|
}
|