@capgo/camera-preview 7.4.0-alpha.13 → 7.4.0-alpha.18

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
@@ -262,6 +262,10 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
262
262
  * [`setPreviewSize(...)`](#setpreviewsize)
263
263
  * [`setFocus(...)`](#setfocus)
264
264
  * [`addListener('screenResize', ...)`](#addlistenerscreenresize-)
265
+ * [`addListener('orientationChange', ...)`](#addlistenerorientationchange-)
266
+ * [`deleteFile(...)`](#deletefile)
267
+ * [`getSafeAreaInsets()`](#getsafeareainsets)
268
+ * [`getOrientation()`](#getorientation)
265
269
  * [Interfaces](#interfaces)
266
270
  * [Type Aliases](#type-aliases)
267
271
  * [Enums](#enums)
@@ -372,7 +376,7 @@ Set the aspect ratio of the camera preview.
372
376
 
373
377
  **Returns:** <code>Promise&lt;{ width: number; height: number; x: number; y: number; }&gt;</code>
374
378
 
375
- **Since:** 7.4.0
379
+ **Since:** 7.5.0
376
380
 
377
381
  --------------------
378
382
 
@@ -387,7 +391,7 @@ Gets the current aspect ratio of the camera preview.
387
391
 
388
392
  **Returns:** <code>Promise&lt;{ aspectRatio: '4:3' | '16:9'; }&gt;</code>
389
393
 
390
- **Since:** 7.4.0
394
+ **Since:** 7.5.0
391
395
 
392
396
  --------------------
393
397
 
@@ -544,7 +548,7 @@ Checks if the camera preview is currently running.
544
548
 
545
549
  **Returns:** <code>Promise&lt;{ isRunning: boolean; }&gt;</code>
546
550
 
547
- **Since:** 7.4.0
551
+ **Since:** 7.5.0
548
552
 
549
553
  --------------------
550
554
 
@@ -559,7 +563,7 @@ Gets all available camera devices.
559
563
 
560
564
  **Returns:** <code>Promise&lt;{ devices: CameraDevice[]; }&gt;</code>
561
565
 
562
- **Since:** 7.4.0
566
+ **Since:** 7.5.0
563
567
 
564
568
  --------------------
565
569
 
@@ -574,7 +578,7 @@ Gets the current zoom state, including min/max and current lens info.
574
578
 
575
579
  **Returns:** <code>Promise&lt;{ min: number; max: number; current: number; lens: <a href="#lensinfo">LensInfo</a>; }&gt;</code>
576
580
 
577
- **Since:** 7.4.0
581
+ **Since:** 7.5.0
578
582
 
579
583
  --------------------
580
584
 
@@ -591,6 +595,8 @@ Returns zoom button values for quick switching.
591
595
 
592
596
  **Returns:** <code>Promise&lt;{ values: number[]; }&gt;</code>
593
597
 
598
+ **Since:** 7.5.0
599
+
594
600
  --------------------
595
601
 
596
602
 
@@ -606,7 +612,7 @@ Sets the zoom level of the camera.
606
612
  | ------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
607
613
  | **`options`** | <code>{ level: number; ramp?: boolean; autoFocus?: boolean; }</code> | - The desired zoom level. `ramp` is currently unused. `autoFocus` defaults to true. |
608
614
 
609
- **Since:** 7.4.0
615
+ **Since:** 7.5.0
610
616
 
611
617
  --------------------
612
618
 
@@ -621,7 +627,7 @@ Gets the current flash mode.
621
627
 
622
628
  **Returns:** <code>Promise&lt;{ flashMode: <a href="#camerapreviewflashmode">CameraPreviewFlashMode</a>; }&gt;</code>
623
629
 
624
- **Since:** 7.4.0
630
+ **Since:** 7.5.0
625
631
 
626
632
  --------------------
627
633
 
@@ -634,7 +640,7 @@ removeAllListeners() => Promise<void>
634
640
 
635
641
  Removes all registered listeners.
636
642
 
637
- **Since:** 7.4.0
643
+ **Since:** 7.5.0
638
644
 
639
645
  --------------------
640
646
 
@@ -651,7 +657,7 @@ Switches the active camera to the one with the specified `deviceId`.
651
657
  | ------------- | ---------------------------------- | ------------------------------------ |
652
658
  | **`options`** | <code>{ deviceId: string; }</code> | - The ID of the device to switch to. |
653
659
 
654
- **Since:** 7.4.0
660
+ **Since:** 7.5.0
655
661
 
656
662
  --------------------
657
663
 
@@ -666,7 +672,7 @@ Gets the ID of the currently active camera device.
666
672
 
667
673
  **Returns:** <code>Promise&lt;{ deviceId: string; }&gt;</code>
668
674
 
669
- **Since:** 7.4.0
675
+ **Since:** 7.5.0
670
676
 
671
677
  --------------------
672
678
 
@@ -681,6 +687,8 @@ Gets the current preview size and position.
681
687
 
682
688
  **Returns:** <code>Promise&lt;{ x: number; y: number; width: number; height: number; }&gt;</code>
683
689
 
690
+ **Since:** 7.5.0
691
+
684
692
  --------------------
685
693
 
686
694
 
@@ -698,6 +706,8 @@ Sets the preview size and position.
698
706
 
699
707
  **Returns:** <code>Promise&lt;{ width: number; height: number; x: number; y: number; }&gt;</code>
700
708
 
709
+ **Since:** 7.5.0
710
+
701
711
  --------------------
702
712
 
703
713
 
@@ -713,7 +723,7 @@ Sets the camera focus to a specific point in the preview.
713
723
  | ------------- | -------------------------------------- | -------------------- |
714
724
  | **`options`** | <code>{ x: number; y: number; }</code> | - The focus options. |
715
725
 
716
- **Since:** 8.1.0
726
+ **Since:** 7.5.0
717
727
 
718
728
  --------------------
719
729
 
@@ -724,13 +734,87 @@ Sets the camera focus to a specific point in the preview.
724
734
  addListener(eventName: "screenResize", listenerFunc: (data: { width: number; height: number; x: number; y: number; }) => void) => Promise<PluginListenerHandle>
725
735
  ```
726
736
 
727
- | Param | Type |
728
- | ------------------ | ---------------------------------------------------------------------------------------- |
729
- | **`eventName`** | <code>'screenResize'</code> |
730
- | **`listenerFunc`** | <code>(data: { width: number; height: number; x: number; y: number; }) =&gt; void</code> |
737
+ Adds a listener for screen resize events.
738
+
739
+ | Param | Type | Description |
740
+ | ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------------------- |
741
+ | **`eventName`** | <code>'screenResize'</code> | - The event name to listen for. |
742
+ | **`listenerFunc`** | <code>(data: { width: number; height: number; x: number; y: number; }) =&gt; void</code> | - The function to call when the event is triggered. |
731
743
 
732
744
  **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
733
745
 
746
+ **Since:** 7.5.0
747
+
748
+ --------------------
749
+
750
+
751
+ ### addListener('orientationChange', ...)
752
+
753
+ ```typescript
754
+ addListener(eventName: "orientationChange", listenerFunc: (data: { orientation: DeviceOrientation; }) => void) => Promise<PluginListenerHandle>
755
+ ```
756
+
757
+ Adds a listener for orientation change events.
758
+
759
+ | Param | Type | Description |
760
+ | ------------------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
761
+ | **`eventName`** | <code>'orientationChange'</code> | - The event name to listen for. |
762
+ | **`listenerFunc`** | <code>(data: { orientation: <a href="#deviceorientation">DeviceOrientation</a>; }) =&gt; void</code> | - The function to call when the event is triggered. |
763
+
764
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
765
+
766
+ **Since:** 7.5.0
767
+
768
+ --------------------
769
+
770
+
771
+ ### deleteFile(...)
772
+
773
+ ```typescript
774
+ deleteFile(options: { path: string; }) => Promise<{ success: boolean; }>
775
+ ```
776
+
777
+ Deletes a file at the given absolute path on the device.
778
+ Use this to quickly clean up temporary images created with `storeToFile`.
779
+ On web, this is not supported and will throw.
780
+
781
+ | Param | Type |
782
+ | ------------- | ------------------------------ |
783
+ | **`options`** | <code>{ path: string; }</code> |
784
+
785
+ **Returns:** <code>Promise&lt;{ success: boolean; }&gt;</code>
786
+
787
+ **Since:** 7.5.0
788
+
789
+ --------------------
790
+
791
+
792
+ ### getSafeAreaInsets()
793
+
794
+ ```typescript
795
+ getSafeAreaInsets() => Promise<SafeAreaInsets>
796
+ ```
797
+
798
+ Gets the safe area insets for Android devices.
799
+ Returns the top and bottom insets in dp and the current orientation.
800
+
801
+ **Returns:** <code>Promise&lt;<a href="#safeareainsets">SafeAreaInsets</a>&gt;</code>
802
+
803
+ --------------------
804
+
805
+
806
+ ### getOrientation()
807
+
808
+ ```typescript
809
+ getOrientation() => Promise<{ orientation: DeviceOrientation; }>
810
+ ```
811
+
812
+ Gets the current device orientation in a cross-platform format.
813
+
814
+ **Returns:** <code>Promise&lt;{ orientation: <a href="#deviceorientation">DeviceOrientation</a>; }&gt;</code>
815
+
816
+ **Since:** 7.5.0
817
+
734
818
  --------------------
735
819
 
736
820
 
@@ -874,6 +958,18 @@ Represents the detailed information of the currently active lens.
874
958
  | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
875
959
 
876
960
 
961
+ #### SafeAreaInsets
962
+
963
+ Represents safe area insets on Android.
964
+ Values are expressed in logical pixels (dp) to match JS layout units.
965
+
966
+ | Prop | Type | Description |
967
+ | ----------------- | ------------------- | ---------------------------------------------------------------- |
968
+ | **`orientation`** | <code>number</code> | Current device orientation as reported by Android configuration. |
969
+ | **`top`** | <code>number</code> | Top inset (e.g., status bar or cutout) in dp. |
970
+ | **`bottom`** | <code>number</code> | Bottom inset (e.g., navigation bar) in dp. |
971
+
972
+
877
973
  ### Type Aliases
878
974
 
879
975
 
@@ -910,6 +1006,13 @@ The available flash modes for the camera.
910
1006
  <code><a href="#camerapreviewflashmode">CameraPreviewFlashMode</a></code>
911
1007
 
912
1008
 
1009
+ #### DeviceOrientation
1010
+
1011
+ Canonical device orientation values across platforms.
1012
+
1013
+ <code>"portrait" | "landscape" | "landscape-left" | "landscape-right" | "portrait-upside-down" | "unknown"</code>
1014
+
1015
+
913
1016
  ### Enums
914
1017
 
915
1018
 
@@ -5,13 +5,18 @@ import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
6
  import android.Manifest;
7
7
  import android.content.pm.ActivityInfo;
8
+ import android.content.res.Configuration;
8
9
  import android.location.Location;
9
10
  import android.util.DisplayMetrics;
10
11
  import android.util.Log;
11
12
  import android.util.Size;
13
+ import android.view.OrientationEventListener;
12
14
  import android.view.View;
13
15
  import android.view.ViewGroup;
14
16
  import android.webkit.WebView;
17
+ import androidx.core.graphics.Insets;
18
+ import androidx.core.view.ViewCompat;
19
+ import androidx.core.view.WindowInsetsCompat;
15
20
  import com.ahm.capacitor.camera.preview.model.CameraDevice;
16
21
  import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
17
22
  import com.ahm.capacitor.camera.preview.model.LensInfo;
@@ -69,6 +74,28 @@ public class CameraPreview
69
74
  private CameraXView cameraXView;
70
75
  private FusedLocationProviderClient fusedLocationClient;
71
76
  private Location lastLocation;
77
+ private OrientationEventListener orientationListener;
78
+ private int lastOrientation = Configuration.ORIENTATION_UNDEFINED;
79
+
80
+ @PluginMethod
81
+ public void getOrientation(PluginCall call) {
82
+ int orientation = getContext()
83
+ .getResources()
84
+ .getConfiguration()
85
+ .orientation;
86
+ String o;
87
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
88
+ // We don't distinguish upside-down reliably on Android, report generic portrait
89
+ o = "portrait";
90
+ } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
91
+ o = "landscape";
92
+ } else {
93
+ o = "unknown";
94
+ }
95
+ JSObject ret = new JSObject();
96
+ ret.put("orientation", o);
97
+ call.resolve(ret);
98
+ }
72
99
 
73
100
  @PluginMethod
74
101
  public void start(PluginCall call) {
@@ -205,6 +232,13 @@ public class CameraPreview
205
232
  .getActivity()
206
233
  .setRequestedOrientation(previousOrientationRequest);
207
234
 
235
+ // Disable and clear orientation listener
236
+ if (orientationListener != null) {
237
+ orientationListener.disable();
238
+ orientationListener = null;
239
+ lastOrientation = Configuration.ORIENTATION_UNDEFINED;
240
+ }
241
+
208
242
  if (cameraXView != null && cameraXView.isRunning()) {
209
243
  cameraXView.stopSession();
210
244
  cameraXView = null;
@@ -215,6 +249,10 @@ public class CameraPreview
215
249
 
216
250
  @PluginMethod
217
251
  public void getSupportedFlashModes(PluginCall call) {
252
+ if (cameraXView == null || !cameraXView.isRunning()) {
253
+ call.reject("Camera is not running");
254
+ return;
255
+ }
218
256
  List<String> supportedFlashModes = cameraXView.getSupportedFlashModes();
219
257
  JSArray jsonFlashModes = new JSArray();
220
258
  for (String mode : supportedFlashModes) {
@@ -268,6 +306,10 @@ public class CameraPreview
268
306
 
269
307
  @PluginMethod
270
308
  public void getZoom(PluginCall call) {
309
+ if (cameraXView == null || !cameraXView.isRunning()) {
310
+ call.reject("Camera is not running");
311
+ return;
312
+ }
271
313
  ZoomFactors zoomFactors = cameraXView.getZoomFactors();
272
314
  JSObject result = new JSObject();
273
315
  result.put("min", zoomFactors.getMin());
@@ -278,6 +320,10 @@ public class CameraPreview
278
320
 
279
321
  @PluginMethod
280
322
  public void getZoomButtonValues(PluginCall call) {
323
+ if (cameraXView == null || !cameraXView.isRunning()) {
324
+ call.reject("Camera is not running");
325
+ return;
326
+ }
281
327
  // Build a sorted set to dedupe and order ascending
282
328
  java.util.Set<Double> sorted = new java.util.TreeSet<>();
283
329
  sorted.add(1.0);
@@ -968,6 +1014,67 @@ public class CameraPreview
968
1014
  bridge.saveCall(call);
969
1015
  cameraStartCallbackId = call.getCallbackId();
970
1016
  cameraXView.startSession(config);
1017
+
1018
+ // Setup orientation listener to mirror iOS screenResize emission
1019
+ if (orientationListener == null) {
1020
+ lastOrientation = getContext()
1021
+ .getResources()
1022
+ .getConfiguration()
1023
+ .orientation;
1024
+ orientationListener = new OrientationEventListener(getContext()) {
1025
+ @Override
1026
+ public void onOrientationChanged(int orientation) {
1027
+ if (orientation == ORIENTATION_UNKNOWN) return;
1028
+ int current = getContext()
1029
+ .getResources()
1030
+ .getConfiguration()
1031
+ .orientation;
1032
+ if (current != lastOrientation) {
1033
+ lastOrientation = current;
1034
+ handleOrientationChange();
1035
+ }
1036
+ }
1037
+ };
1038
+ if (orientationListener.canDetectOrientation()) {
1039
+ orientationListener.enable();
1040
+ }
1041
+ }
1042
+ });
1043
+ }
1044
+
1045
+ private void handleOrientationChange() {
1046
+ if (cameraXView == null || !cameraXView.isRunning()) return;
1047
+ getBridge()
1048
+ .getActivity()
1049
+ .runOnUiThread(() -> {
1050
+ // Reapply current aspect ratio to recompute layout, then emit screenResize
1051
+ String ar = cameraXView.getAspectRatio();
1052
+ cameraXView.setAspectRatio(ar, null, null, () -> {
1053
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
1054
+ JSObject data = new JSObject();
1055
+ data.put("x", bounds[0]);
1056
+ data.put("y", bounds[1]);
1057
+ data.put("width", bounds[2]);
1058
+ data.put("height", bounds[3]);
1059
+ notifyListeners("screenResize", data);
1060
+
1061
+ // Also emit orientationChange with a unified string value
1062
+ int current = getContext()
1063
+ .getResources()
1064
+ .getConfiguration()
1065
+ .orientation;
1066
+ String o;
1067
+ if (current == Configuration.ORIENTATION_PORTRAIT) {
1068
+ o = "portrait";
1069
+ } else if (current == Configuration.ORIENTATION_LANDSCAPE) {
1070
+ o = "landscape";
1071
+ } else {
1072
+ o = "unknown";
1073
+ }
1074
+ JSObject oData = new JSObject();
1075
+ oData.put("orientation", o);
1076
+ notifyListeners("orientationChange", oData);
1077
+ });
971
1078
  });
972
1079
  }
973
1080
 
@@ -1255,4 +1362,120 @@ public class CameraPreview
1255
1362
  call.reject("Failed to delete file: " + e.getMessage());
1256
1363
  }
1257
1364
  }
1365
+
1366
+ @PluginMethod
1367
+ public void getSafeAreaInsets(PluginCall call) {
1368
+ JSObject ret = new JSObject();
1369
+ int orientation = getContext()
1370
+ .getResources()
1371
+ .getConfiguration()
1372
+ .orientation;
1373
+
1374
+ int topPx = 0;
1375
+ int bottomPx = 0;
1376
+ try {
1377
+ View webView = getBridge().getWebView();
1378
+ if (webView != null) {
1379
+ DisplayMetrics metrics = getBridge()
1380
+ .getActivity()
1381
+ .getResources()
1382
+ .getDisplayMetrics();
1383
+ int screenHeight = metrics.heightPixels;
1384
+ int[] location = new int[2];
1385
+ webView.getLocationOnScreen(location);
1386
+ int webViewTop = location[1];
1387
+ int webViewBottom = webViewTop + webView.getHeight();
1388
+ int webViewBottomGap = Math.max(0, screenHeight - webViewBottom);
1389
+
1390
+ // System insets (status/navigation/cutout)
1391
+ int systemTop = 0;
1392
+ int systemBottom = 0;
1393
+ View decorView = getBridge().getActivity().getWindow().getDecorView();
1394
+ WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
1395
+ if (insets != null) {
1396
+ Insets sysBars = insets.getInsets(
1397
+ WindowInsetsCompat.Type.systemBars()
1398
+ );
1399
+ Insets cutout = insets.getInsets(
1400
+ WindowInsetsCompat.Type.displayCutout()
1401
+ );
1402
+ systemTop = Math.max(sysBars.top, cutout.top);
1403
+ systemBottom = Math.max(sysBars.bottom, cutout.bottom);
1404
+ } else {
1405
+ systemTop = getStatusBarHeightPx();
1406
+ systemBottom = getNavigationBarHeightPx();
1407
+ }
1408
+
1409
+ // Top: report the gap between screen and WebView (useful when not edge-to-edge)
1410
+ topPx = Math.max(0, webViewTop);
1411
+
1412
+ // Bottom logic:
1413
+ // - If WebView has a bottom gap equal to the system nav bar height (3-button mode),
1414
+ // it means layout already accounts for it -> return 0 as requested.
1415
+ // - If WebView has no gap (edge-to-edge or overlay), return system bottom inset.
1416
+ // - Otherwise, default to system bottom inset (avoid counting app UI like tab bars).
1417
+ if (
1418
+ webViewBottomGap > 0 && approxEqualPx(webViewBottomGap, systemBottom)
1419
+ ) {
1420
+ bottomPx = 0; // already offset by system nav bar
1421
+ } else if (webViewBottomGap == 0) {
1422
+ bottomPx = systemBottom;
1423
+ } else {
1424
+ bottomPx = systemBottom;
1425
+ }
1426
+ } else {
1427
+ // Fallback if WebView is unavailable
1428
+ View decorView = getBridge().getActivity().getWindow().getDecorView();
1429
+ WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
1430
+ if (insets != null) {
1431
+ Insets sysBars = insets.getInsets(
1432
+ WindowInsetsCompat.Type.systemBars()
1433
+ );
1434
+ Insets cutout = insets.getInsets(
1435
+ WindowInsetsCompat.Type.displayCutout()
1436
+ );
1437
+ topPx = Math.max(sysBars.top, cutout.top);
1438
+ bottomPx = Math.max(sysBars.bottom, cutout.bottom);
1439
+ } else {
1440
+ topPx = getStatusBarHeightPx();
1441
+ bottomPx = getNavigationBarHeightPx();
1442
+ }
1443
+ }
1444
+ } catch (Exception e) {
1445
+ topPx = getStatusBarHeightPx();
1446
+ bottomPx = getNavigationBarHeightPx();
1447
+ }
1448
+
1449
+ float density = getContext().getResources().getDisplayMetrics().density;
1450
+ ret.put("orientation", orientation);
1451
+ ret.put("top", topPx / density);
1452
+ ret.put("bottom", bottomPx / density);
1453
+ call.resolve(ret);
1454
+ }
1455
+
1456
+ private boolean approxEqualPx(int a, int b) {
1457
+ return Math.abs(a - b) <= 2; // within 2px tolerance
1458
+ }
1459
+
1460
+ private int getStatusBarHeightPx() {
1461
+ int result = 0;
1462
+ int resourceId = getContext()
1463
+ .getResources()
1464
+ .getIdentifier("status_bar_height", "dimen", "android");
1465
+ if (resourceId > 0) {
1466
+ result = getContext().getResources().getDimensionPixelSize(resourceId);
1467
+ }
1468
+ return result;
1469
+ }
1470
+
1471
+ private int getNavigationBarHeightPx() {
1472
+ int result = 0;
1473
+ int resourceId = getContext()
1474
+ .getResources()
1475
+ .getIdentifier("navigation_bar_height", "dimen", "android");
1476
+ if (resourceId > 0) {
1477
+ result = getContext().getResources().getDimensionPixelSize(resourceId);
1478
+ }
1479
+ return result;
1480
+ }
1258
1481
  }