@capgo/camera-preview 7.4.0-beta.13 → 7.4.0-beta.16

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
@@ -562,14 +562,14 @@ Gets the current zoom state, including min/max and current lens info.
562
562
  ### setZoom(...)
563
563
 
564
564
  ```typescript
565
- setZoom(options: { level: number; ramp?: boolean; }) => Promise<void>
565
+ setZoom(options: { level: number; ramp?: boolean; autoFocus?: boolean; }) => Promise<void>
566
566
  ```
567
567
 
568
- Sets the camera's zoom level.
568
+ Sets the zoom level of the camera.
569
569
 
570
- | Param | Type | Description |
571
- | ------------- | ----------------------------------------------- | ----------------------------------------------------- |
572
- | **`options`** | <code>{ level: number; ramp?: boolean; }</code> | - The desired zoom level. `ramp` is currently unused. |
570
+ | Param | Type | Description |
571
+ | ------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
572
+ | **`options`** | <code>{ level: number; ramp?: boolean; autoFocus?: boolean; }</code> | - The desired zoom level. `ramp` is currently unused. `autoFocus` defaults to true. |
573
573
 
574
574
  **Since:** 7.4.0
575
575
 
@@ -652,14 +652,14 @@ Gets the current preview size and position.
652
652
  ### setPreviewSize(...)
653
653
 
654
654
  ```typescript
655
- setPreviewSize(options: { x: number; y: number; width: number; height: number; }) => Promise<{ width: number; height: number; x: number; y: number; }>
655
+ setPreviewSize(options: { x?: number; y?: number; width: number; height: number; }) => Promise<{ width: number; height: number; x: number; y: number; }>
656
656
  ```
657
657
 
658
658
  Sets the preview size and position.
659
659
 
660
- | Param | Type | Description |
661
- | ------------- | --------------------------------------------------------------------- | -------------------------------- |
662
- | **`options`** | <code>{ x: number; y: number; width: number; height: number; }</code> | The new position and dimensions. |
660
+ | Param | Type | Description |
661
+ | ------------- | ----------------------------------------------------------------------- | -------------------------------- |
662
+ | **`options`** | <code>{ x?: number; y?: number; width: number; height: number; }</code> | The new position and dimensions. |
663
663
 
664
664
  **Returns:** <code>Promise&lt;{ width: number; height: number; x: number; y: number; }&gt;</code>
665
665
 
@@ -707,13 +707,13 @@ Defines the configuration options for starting the camera preview.
707
707
  | **`position`** | <code>string</code> | The camera to use. | <code>"rear"</code> | |
708
708
  | **`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> | |
709
709
  | **`disableExifHeaderStripping`** | <code>boolean</code> | If true, prevents the plugin from rotating the image based on EXIF data. | <code>false</code> | |
710
- | **`enableHighResolution`** | <code>boolean</code> | If true, enables high-resolution image capture. | <code>false</code> | |
711
710
  | **`disableAudio`** | <code>boolean</code> | If true, disables the audio stream, preventing audio permission requests. | <code>true</code> | |
712
711
  | **`lockAndroidOrientation`** | <code>boolean</code> | If true, locks the device orientation while the camera is active. | <code>false</code> | |
713
712
  | **`enableOpacity`** | <code>boolean</code> | If true, allows the camera preview's opacity to be changed. | <code>false</code> | |
714
713
  | **`enableZoom`** | <code>boolean</code> | If true, enables pinch-to-zoom functionality on the preview. | <code>false</code> | |
715
714
  | **`enableVideoMode`** | <code>boolean</code> | If true, uses the video-optimized preset for the camera session. | <code>false</code> | |
716
715
  | **`deviceId`** | <code>string</code> | The `deviceId` of the camera to use. If provided, `position` is ignored. | | |
716
+ | **`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 |
717
717
 
718
718
 
719
719
  #### ExifData
@@ -725,14 +725,15 @@ Represents EXIF data extracted from an image.
725
725
 
726
726
  Defines the options for capturing a picture.
727
727
 
728
- | Prop | Type | Description | Default | Since |
729
- | ---------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ----- |
730
- | **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | | |
731
- | **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | | |
732
- | **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> | |
733
- | **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> | |
734
- | **`saveToGallery`** | <code>boolean</code> | If true, the captured image will be saved to the user's gallery. | <code>false</code> | 7.5.0 |
735
- | **`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 |
728
+ | Prop | Type | Description | Default | Since |
729
+ | ---------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----- |
730
+ | **`height`** | <code>number</code> | The desired height of the picture in pixels. If not provided, the device default is used. | | |
731
+ | **`width`** | <code>number</code> | The desired width of the picture in pixels. If not provided, the device default is used. | | |
732
+ | **`aspectRatio`** | <code>string</code> | The desired aspect ratio of the captured image (e.g., '4:3', '16:9'). If specified without width/height, captures the largest possible image with this ratio. Cannot be used together with width or height - the capture will be rejected with an error. | | 7.7.0 |
733
+ | **`quality`** | <code>number</code> | The quality of the captured image, from 0 to 100. Does not apply to `png` format. | <code>85</code> | |
734
+ | **`format`** | <code><a href="#pictureformat">PictureFormat</a></code> | The format of the captured image. | <code>"jpeg"</code> | |
735
+ | **`saveToGallery`** | <code>boolean</code> | If true, the captured image will be saved to the user's gallery. | <code>false</code> | 7.5.0 |
736
+ | **`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 |
736
737
 
737
738
 
738
739
  #### CameraSampleOptions
Binary file
@@ -9,7 +9,9 @@ import android.location.Location;
9
9
  import android.util.DisplayMetrics;
10
10
  import android.util.Log;
11
11
  import android.util.Size;
12
+ import android.view.View;
12
13
  import android.view.ViewGroup;
14
+ import android.webkit.WebView;
13
15
  import com.ahm.capacitor.camera.preview.model.CameraDevice;
14
16
  import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
15
17
  import com.ahm.capacitor.camera.preview.model.LensInfo;
@@ -170,8 +172,9 @@ public class CameraPreview
170
172
  final boolean saveToGallery = call.getBoolean("saveToGallery", false);
171
173
  Integer width = call.getInt("width");
172
174
  Integer height = call.getInt("height");
175
+ String aspectRatio = call.getString("aspectRatio");
173
176
 
174
- cameraXView.capturePhoto(quality, saveToGallery, width, height, location);
177
+ cameraXView.capturePhoto(quality, saveToGallery, width, height, aspectRatio, location);
175
178
  }
176
179
 
177
180
  @PluginMethod
@@ -277,8 +280,9 @@ public class CameraPreview
277
280
  call.reject("level parameter is required");
278
281
  return;
279
282
  }
283
+ Boolean autoFocus = call.getBoolean("autoFocus", true);
280
284
  try {
281
- cameraXView.setZoom(level);
285
+ cameraXView.setZoom(level, autoFocus);
282
286
  call.resolve();
283
287
  } catch (Exception e) {
284
288
  call.reject("Failed to set zoom: " + e.getMessage());
@@ -297,14 +301,16 @@ public class CameraPreview
297
301
  call.reject("x and y parameters are required");
298
302
  return;
299
303
  }
300
- // Ensure values are between 0 and 1
301
- float normalizedX = Math.max(0f, Math.min(1f, x));
302
- float normalizedY = Math.max(0f, Math.min(1f, y));
304
+ // Reject if values are outside 0-1 range
305
+ if (x < 0f || x > 1f || y < 0f || y > 1f) {
306
+ call.reject("Focus coordinates must be between 0 and 1");
307
+ return;
308
+ }
303
309
 
304
310
  getActivity()
305
311
  .runOnUiThread(() -> {
306
312
  try {
307
- cameraXView.setFocus(normalizedX, normalizedY);
313
+ cameraXView.setFocus(x, y);
308
314
  call.resolve();
309
315
  } catch (Exception e) {
310
316
  call.reject("Failed to set focus: " + e.getMessage());
@@ -439,8 +445,30 @@ public class CameraPreview
439
445
  "back".equals(positionParam))
440
446
  ? "back"
441
447
  : "front";
442
- final int x = call.getInt("x", 0);
443
- final int y = call.getInt("y", 0);
448
+ // Use -1 as default to indicate centering is needed when x/y not provided
449
+ final Integer xParam = call.getInt("x");
450
+ final Integer yParam = call.getInt("y");
451
+ final int x = xParam != null ? xParam : -1;
452
+ final int y = yParam != null ? yParam : -1;
453
+
454
+ Log.d("CameraPreview", "========================");
455
+ Log.d("CameraPreview", "CAMERA POSITION TRACKING START:");
456
+ Log.d(
457
+ "CameraPreview",
458
+ "1. RAW PARAMS - xParam: " + xParam + ", yParam: " + yParam
459
+ );
460
+ Log.d(
461
+ "CameraPreview",
462
+ "2. AFTER DEFAULT - x: " +
463
+ x +
464
+ " (center=" +
465
+ (x == -1) +
466
+ "), y: " +
467
+ y +
468
+ " (center=" +
469
+ (y == -1) +
470
+ ")"
471
+ );
444
472
  final int width = call.getInt("width", 0);
445
473
  final int height = call.getInt("height", 0);
446
474
  final int paddingBottom = call.getInt("paddingBottom", 0);
@@ -465,6 +493,7 @@ public class CameraPreview
465
493
  );
466
494
  final String aspectRatio = call.getString("aspectRatio", "4:3");
467
495
  final String gridMode = call.getString("gridMode", "none");
496
+ final float initialZoomLevel = call.getFloat("initialZoomLevel", 1.0f);
468
497
 
469
498
  // Check for conflict between aspectRatio and size
470
499
  if (
@@ -477,7 +506,7 @@ public class CameraPreview
477
506
  return;
478
507
  }
479
508
 
480
- float targetZoom = 1.0f;
509
+ float targetZoom = initialZoomLevel;
481
510
  // Check if the selected device is a physical ultra-wide
482
511
  if (originalDeviceId != null) {
483
512
  List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
@@ -547,11 +576,162 @@ public class CameraPreview
547
576
 
548
577
  // Calculate pixel ratio
549
578
  float pixelRatio = metrics.density;
579
+
580
+ // The key insight: JavaScript coordinates are relative to the WebView's viewport
581
+ // If the WebView is positioned below the status bar (webViewLocationOnScreen[1] > 0),
582
+ // we need to add that offset when placing native views
583
+ int webViewTopInset = webViewLocationOnScreen[1];
584
+ boolean isEdgeToEdgeActive = webViewLocationOnScreen[1] > 0;
585
+
586
+ // Log all the positioning information for debugging
587
+ Log.d("CameraPreview", "WebView Position Debug:");
588
+ Log.d("CameraPreview", " - webView.getTop(): " + webViewTop);
589
+ Log.d("CameraPreview", " - webView.getLeft(): " + webViewLeft);
590
+ Log.d("CameraPreview", " - webView locationInWindow: (" + webViewLocationInWindow[0] + ", " + webViewLocationInWindow[1] + ")");
591
+ Log.d("CameraPreview", " - webView locationOnScreen: (" + webViewLocationOnScreen[0] + ", " + webViewLocationOnScreen[1] + ")");
592
+ Log.d("CameraPreview", " - parent locationInWindow: (" + parentLocationInWindow[0] + ", " + parentLocationInWindow[1] + ")");
593
+ Log.d("CameraPreview", " - parent locationOnScreen: (" + parentLocationOnScreen[0] + ", " + parentLocationOnScreen[1] + ")");
594
+
595
+ // Check if WebView has margins
596
+ View webView = getBridge().getWebView();
597
+ ViewGroup.LayoutParams webViewLayoutParams = webView.getLayoutParams();
598
+ if (webViewLayoutParams instanceof ViewGroup.MarginLayoutParams) {
599
+ ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) webViewLayoutParams;
600
+ Log.d("CameraPreview", " - webView margins: left=" + marginParams.leftMargin +
601
+ ", top=" + marginParams.topMargin +
602
+ ", right=" + marginParams.rightMargin +
603
+ ", bottom=" + marginParams.bottomMargin);
604
+ }
605
+
606
+ // Check WebView padding
607
+ Log.d("CameraPreview", " - webView padding: left=" + webView.getPaddingLeft() +
608
+ ", top=" + webView.getPaddingTop() +
609
+ ", right=" + webView.getPaddingRight() +
610
+ ", bottom=" + webView.getPaddingBottom());
611
+
612
+ Log.d("CameraPreview", " - Using webViewTopInset: " + webViewTopInset);
613
+ Log.d("CameraPreview", " - isEdgeToEdgeActive: " + isEdgeToEdgeActive);
614
+
615
+ // Calculate position - center if x or y is -1
616
+ int computedX;
617
+ int computedY;
618
+
619
+ // Calculate dimensions first
620
+ int computedWidth = width != 0
621
+ ? (int) (width * pixelRatio)
622
+ : getBridge().getWebView().getWidth();
623
+ int computedHeight = height != 0
624
+ ? (int) (height * pixelRatio)
625
+ : getBridge().getWebView().getHeight();
626
+ computedHeight -= (int) (paddingBottom * pixelRatio);
550
627
 
551
- // Try using just the pixel ratio without any webview offset for now
552
- int computedX = (int) (x * pixelRatio);
553
- int computedY = (int) (y * pixelRatio);
628
+ Log.d("CameraPreview", "========================");
629
+ Log.d("CameraPreview", "POSITIONING CALCULATIONS:");
630
+ Log.d("CameraPreview", "1. INPUT - x: " + x + ", y: " + y + ", width: " + width + ", height: " + height);
631
+ Log.d("CameraPreview", "2. PIXEL RATIO: " + pixelRatio);
632
+ Log.d("CameraPreview", "3. SCREEN - width: " + metrics.widthPixels + ", height: " + metrics.heightPixels);
633
+ Log.d("CameraPreview", "4. WEBVIEW - width: " + getBridge().getWebView().getWidth() + ", height: " + getBridge().getWebView().getHeight());
634
+ Log.d("CameraPreview", "5. COMPUTED DIMENSIONS - width: " + computedWidth + ", height: " + computedHeight);
635
+
636
+ if (x == -1) {
637
+ // Center horizontally
638
+ int screenWidth = metrics.widthPixels;
639
+ computedX = (screenWidth - computedWidth) / 2;
640
+ Log.d(
641
+ "CameraPreview",
642
+ "Centering horizontally: screenWidth=" +
643
+ screenWidth +
644
+ ", computedWidth=" +
645
+ computedWidth +
646
+ ", computedX=" +
647
+ computedX
648
+ );
649
+ } else {
650
+ computedX = (int) (x * pixelRatio);
651
+ Log.d(
652
+ "CameraPreview",
653
+ "Using provided X position: " +
654
+ x +
655
+ " * " +
656
+ pixelRatio +
657
+ " = " +
658
+ computedX
659
+ );
660
+ }
661
+
662
+ if (y == -1) {
663
+ // Center vertically
664
+ if (isEdgeToEdgeActive) {
665
+ // When WebView is offset from top, center within the available space
666
+ // The camera should be centered in the full screen, not just the WebView area
667
+ computedY = (metrics.heightPixels - computedHeight) / 2;
668
+ Log.d(
669
+ "CameraPreview",
670
+ "Centering vertically with WebView offset: screenHeight=" +
671
+ metrics.heightPixels +
672
+ ", webViewTop=" +
673
+ webViewTopInset +
674
+ ", computedHeight=" +
675
+ computedHeight +
676
+ ", computedY=" +
677
+ computedY
678
+ );
679
+ } else {
680
+ // Normal mode - use full screen height
681
+ computedY = (metrics.heightPixels - computedHeight) / 2;
682
+ Log.d(
683
+ "CameraPreview",
684
+ "Centering vertically (normal): screenHeight=" +
685
+ metrics.heightPixels +
686
+ ", computedHeight=" +
687
+ computedHeight +
688
+ ", computedY=" +
689
+ computedY
690
+ );
691
+ }
692
+ } else {
693
+ computedY = (int) (y * pixelRatio);
694
+ // If edge-to-edge is active, JavaScript Y is relative to WebView content area
695
+ // We need to add the inset to get absolute screen position
696
+ if (isEdgeToEdgeActive) {
697
+ computedY += webViewTopInset;
698
+ Log.d(
699
+ "CameraPreview",
700
+ "Edge-to-edge adjustment: Y position " +
701
+ (int)(y * pixelRatio) +
702
+ " + inset " +
703
+ webViewTopInset +
704
+ " = " +
705
+ computedY
706
+ );
707
+ }
708
+ Log.d(
709
+ "CameraPreview",
710
+ "Using provided Y position: " +
711
+ y +
712
+ " * " +
713
+ pixelRatio +
714
+ " = " +
715
+ computedY +
716
+ (isEdgeToEdgeActive ? " (adjusted for edge-to-edge)" : "")
717
+ );
718
+ }
554
719
 
720
+ Log.d(
721
+ "CameraPreview",
722
+ "2b. EDGE-TO-EDGE - " + (isEdgeToEdgeActive ? "ACTIVE (inset=" + webViewTopInset + ")" : "INACTIVE")
723
+ );
724
+ Log.d(
725
+ "CameraPreview",
726
+ "3. COMPUTED POSITION - x=" + computedX + ", y=" + computedY
727
+ );
728
+ Log.d(
729
+ "CameraPreview",
730
+ "4. COMPUTED SIZE - width=" +
731
+ computedWidth +
732
+ ", height=" +
733
+ computedHeight
734
+ );
555
735
  Log.d("CameraPreview", "=== COORDINATE DEBUG ===");
556
736
  Log.d(
557
737
  "CameraPreview",
@@ -606,14 +786,11 @@ public class CameraPreview
606
786
  computedY +
607
787
  ")"
608
788
  );
789
+ Log.d("CameraPreview", "5. IS_CENTERED - " + (x == -1 || y == -1));
609
790
  Log.d("CameraPreview", "========================");
610
- int computedWidth = width != 0
611
- ? (int) (width * pixelRatio)
612
- : getBridge().getWebView().getWidth();
613
- int computedHeight = height != 0
614
- ? (int) (height * pixelRatio)
615
- : getBridge().getWebView().getHeight();
616
- computedHeight -= (int) (paddingBottom * pixelRatio);
791
+
792
+ // Pass along whether we're centering so CameraXView knows not to add insets
793
+ boolean isCentered = (x == -1 || y == -1);
617
794
 
618
795
  CameraSessionConfiguration config = new CameraSessionConfiguration(
619
796
  finalDeviceId,
@@ -634,6 +811,7 @@ public class CameraPreview
634
811
  gridMode
635
812
  );
636
813
  config.setTargetZoom(finalTargetZoom);
814
+ config.setCentered(isCentered);
637
815
 
638
816
  bridge.saveCall(call);
639
817
  cameraStartCallbackId = call.getCallbackId();
@@ -677,11 +855,68 @@ public class CameraPreview
677
855
  .getDisplayMetrics();
678
856
  float pixelRatio = metrics.density;
679
857
 
858
+ // When WebView is offset from the top (e.g., below status bar),
859
+ // we need to convert between JavaScript coordinates (relative to WebView)
860
+ // and native coordinates (relative to screen)
861
+ WebView webView = getBridge().getWebView();
862
+ int webViewTopInset = 0;
863
+ boolean isEdgeToEdgeActive = false;
864
+ if (webView != null) {
865
+ int[] location = new int[2];
866
+ webView.getLocationOnScreen(location);
867
+ webViewTopInset = location[1];
868
+ isEdgeToEdgeActive = webViewTopInset > 0;
869
+ }
870
+
871
+ // Only convert to relative position if edge-to-edge is active
872
+ int relativeY = isEdgeToEdgeActive ? (y - webViewTopInset) : y;
873
+
874
+ Log.d("CameraPreview", "========================");
875
+ Log.d("CameraPreview", "CAMERA STARTED - POSITION RETURNED:");
876
+ Log.d(
877
+ "CameraPreview",
878
+ "7. RETURNED (pixels) - x=" +
879
+ x +
880
+ ", y=" +
881
+ y +
882
+ ", width=" +
883
+ width +
884
+ ", height=" +
885
+ height
886
+ );
887
+ Log.d(
888
+ "CameraPreview",
889
+ "8. EDGE-TO-EDGE - " + (isEdgeToEdgeActive ? "ACTIVE" : "INACTIVE")
890
+ );
891
+ Log.d("CameraPreview", "9. WEBVIEW INSET - " + webViewTopInset);
892
+ Log.d(
893
+ "CameraPreview",
894
+ "10. RELATIVE Y - " +
895
+ relativeY +
896
+ " (y=" +
897
+ y +
898
+ (isEdgeToEdgeActive ? " - inset=" + webViewTopInset : " unchanged") +
899
+ ")"
900
+ );
901
+ Log.d(
902
+ "CameraPreview",
903
+ "11. RETURNED (logical) - x=" +
904
+ (x / pixelRatio) +
905
+ ", y=" +
906
+ (relativeY / pixelRatio) +
907
+ ", width=" +
908
+ (width / pixelRatio) +
909
+ ", height=" +
910
+ (height / pixelRatio)
911
+ );
912
+ Log.d("CameraPreview", "12. PIXEL RATIO - " + pixelRatio);
913
+ Log.d("CameraPreview", "========================");
914
+
680
915
  JSObject result = new JSObject();
681
916
  result.put("width", width / pixelRatio);
682
917
  result.put("height", height / pixelRatio);
683
918
  result.put("x", x / pixelRatio);
684
- result.put("y", y / pixelRatio);
919
+ result.put("y", relativeY / pixelRatio);
685
920
  call.resolve(result);
686
921
  bridge.releaseCall(call);
687
922
  cameraStartCallbackId = null; // Prevent re-use
@@ -814,8 +1049,24 @@ public class CameraPreview
814
1049
  .getDisplayMetrics();
815
1050
  float pixelRatio = metrics.density;
816
1051
 
1052
+ // Check if edge-to-edge mode is active
1053
+ WebView webView = getBridge().getWebView();
1054
+ int webViewTopInset = 0;
1055
+ boolean isEdgeToEdgeActive = false;
1056
+ if (webView != null) {
1057
+ int[] location = new int[2];
1058
+ webView.getLocationOnScreen(location);
1059
+ webViewTopInset = location[1];
1060
+ isEdgeToEdgeActive = webViewTopInset > 0;
1061
+ }
1062
+
817
1063
  int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
818
1064
  int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
1065
+
1066
+ // Add edge-to-edge inset to Y if active
1067
+ if (isEdgeToEdgeActive && y > 0) {
1068
+ y += webViewTopInset;
1069
+ }
819
1070
  int width = (widthParam != null && widthParam > 0)
820
1071
  ? (int) (widthParam * pixelRatio)
821
1072
  : 0;