@capgo/camera-preview 7.4.0-beta.2 → 7.4.0-beta.20

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.
Files changed (35) hide show
  1. package/README.md +212 -35
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/file-system.probe +0 -0
  12. package/android/build.gradle +3 -1
  13. package/android/src/main/AndroidManifest.xml +1 -4
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +731 -83
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +2813 -805
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +112 -0
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
  18. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
  19. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +161 -59
  20. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
  21. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
  22. package/dist/docs.json +292 -29
  23. package/dist/esm/definitions.d.ts +148 -13
  24. package/dist/esm/definitions.js.map +1 -1
  25. package/dist/esm/web.d.ts +52 -3
  26. package/dist/esm/web.js +555 -97
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +553 -97
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +553 -97
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Sources/CapgoCameraPreview/CameraController.swift +888 -214
  33. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  34. package/ios/Sources/CapgoCameraPreview/Plugin.swift +967 -250
  35. package/package.json +2 -2
@@ -3,11 +3,22 @@ package com.ahm.capacitor.camera.preview;
3
3
  import static android.Manifest.permission.CAMERA;
4
4
  import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
+ import android.Manifest;
6
7
  import android.content.pm.ActivityInfo;
8
+ import android.location.Location;
7
9
  import android.util.DisplayMetrics;
8
- import android.util.TypedValue;
10
+ import android.util.Log;
11
+ import android.util.Size;
12
+ import android.view.View;
13
+ import android.view.ViewGroup;
14
+ import android.webkit.WebView;
15
+ import com.ahm.capacitor.camera.preview.model.CameraDevice;
16
+ import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
17
+ import com.ahm.capacitor.camera.preview.model.LensInfo;
18
+ import com.ahm.capacitor.camera.preview.model.ZoomFactors;
9
19
  import com.getcapacitor.JSArray;
10
20
  import com.getcapacitor.JSObject;
21
+ import com.getcapacitor.Logger;
11
22
  import com.getcapacitor.PermissionState;
12
23
  import com.getcapacitor.Plugin;
13
24
  import com.getcapacitor.PluginCall;
@@ -15,15 +26,10 @@ import com.getcapacitor.PluginMethod;
15
26
  import com.getcapacitor.annotation.CapacitorPlugin;
16
27
  import com.getcapacitor.annotation.Permission;
17
28
  import com.getcapacitor.annotation.PermissionCallback;
18
- import com.ahm.capacitor.camera.preview.model.CameraDevice;
19
- import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
20
- import com.ahm.capacitor.camera.preview.model.ZoomFactors;
29
+ import com.google.android.gms.location.FusedLocationProviderClient;
30
+ import com.google.android.gms.location.LocationServices;
21
31
  import java.util.List;
22
32
  import java.util.Objects;
23
- import android.util.Size;
24
- import android.util.Log;
25
- import com.ahm.capacitor.camera.preview.model.LensInfo;
26
-
27
33
  import org.json.JSONObject;
28
34
 
29
35
  @CapacitorPlugin(
@@ -37,6 +43,13 @@ import org.json.JSONObject;
37
43
  strings = { CAMERA },
38
44
  alias = CameraPreview.CAMERA_ONLY_PERMISSION_ALIAS
39
45
  ),
46
+ @Permission(
47
+ strings = {
48
+ Manifest.permission.ACCESS_COARSE_LOCATION,
49
+ Manifest.permission.ACCESS_FINE_LOCATION,
50
+ },
51
+ alias = CameraPreview.CAMERA_WITH_LOCATION_PERMISSION_ALIAS
52
+ ),
40
53
  }
41
54
  )
42
55
  public class CameraPreview
@@ -45,16 +58,23 @@ public class CameraPreview
45
58
 
46
59
  static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
47
60
  static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
61
+ static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS =
62
+ "cameraWithLocation";
48
63
 
49
64
  private String captureCallbackId = "";
50
65
  private String snapshotCallbackId = "";
51
66
  private String cameraStartCallbackId = "";
52
- private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
67
+ private int previousOrientationRequest =
68
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
53
69
  private CameraXView cameraXView;
70
+ private FusedLocationProviderClient fusedLocationClient;
71
+ private Location lastLocation;
54
72
 
55
73
  @PluginMethod
56
74
  public void start(PluginCall call) {
57
- boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
75
+ boolean disableAudio = Boolean.TRUE.equals(
76
+ call.getBoolean("disableAudio", true)
77
+ );
58
78
  String permissionAlias = disableAudio
59
79
  ? CAMERA_ONLY_PERMISSION_ALIAS
60
80
  : CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
@@ -87,12 +107,74 @@ public class CameraPreview
87
107
  return;
88
108
  }
89
109
 
110
+ final boolean withExifLocation = call.getBoolean("withExifLocation", false);
111
+
112
+ if (withExifLocation) {
113
+ if (
114
+ getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) !=
115
+ PermissionState.GRANTED
116
+ ) {
117
+ requestPermissionForAlias(
118
+ CAMERA_WITH_LOCATION_PERMISSION_ALIAS,
119
+ call,
120
+ "captureWithLocationPermission"
121
+ );
122
+ } else {
123
+ getLocationAndCapture(call);
124
+ }
125
+ } else {
126
+ captureWithoutLocation(call);
127
+ }
128
+ }
129
+
130
+ @PermissionCallback
131
+ private void captureWithLocationPermission(PluginCall call) {
132
+ if (
133
+ getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) ==
134
+ PermissionState.GRANTED
135
+ ) {
136
+ getLocationAndCapture(call);
137
+ } else {
138
+ Logger.warn(
139
+ "Location permission denied. Capturing photo without location data."
140
+ );
141
+ captureWithoutLocation(call);
142
+ }
143
+ }
144
+
145
+ private void getLocationAndCapture(PluginCall call) {
146
+ if (fusedLocationClient == null) {
147
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(
148
+ getContext()
149
+ );
150
+ }
151
+ fusedLocationClient
152
+ .getLastLocation()
153
+ .addOnSuccessListener(getActivity(), location -> {
154
+ lastLocation = location;
155
+ proceedWithCapture(call, lastLocation);
156
+ })
157
+ .addOnFailureListener(e -> {
158
+ Logger.error("Failed to get location: " + e.getMessage());
159
+ proceedWithCapture(call, null);
160
+ });
161
+ }
162
+
163
+ private void captureWithoutLocation(PluginCall call) {
164
+ proceedWithCapture(call, null);
165
+ }
166
+
167
+ private void proceedWithCapture(PluginCall call, Location location) {
90
168
  bridge.saveCall(call);
91
169
  captureCallbackId = call.getCallbackId();
92
170
 
93
171
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
94
- final boolean saveToGallery = Boolean.TRUE.equals(call.getBoolean("saveToGallery", false));
95
- cameraXView.capturePhoto(quality, saveToGallery);
172
+ final boolean saveToGallery = call.getBoolean("saveToGallery", false);
173
+ Integer width = call.getInt("width");
174
+ Integer height = call.getInt("height");
175
+ String aspectRatio = call.getString("aspectRatio");
176
+
177
+ cameraXView.capturePhoto(quality, saveToGallery, width, height, aspectRatio, location);
96
178
  }
97
179
 
98
180
  @PluginMethod
@@ -111,19 +193,17 @@ public class CameraPreview
111
193
  public void stop(final PluginCall call) {
112
194
  bridge
113
195
  .getActivity()
114
- .runOnUiThread(
115
- () -> {
116
- getBridge()
117
- .getActivity()
118
- .setRequestedOrientation(previousOrientationRequest);
196
+ .runOnUiThread(() -> {
197
+ getBridge()
198
+ .getActivity()
199
+ .setRequestedOrientation(previousOrientationRequest);
119
200
 
120
- if (cameraXView != null && cameraXView.isRunning()) {
121
- cameraXView.stopSession();
122
- cameraXView = null;
123
- }
124
- call.resolve();
201
+ if (cameraXView != null && cameraXView.isRunning()) {
202
+ cameraXView.stopSession();
203
+ cameraXView = null;
125
204
  }
126
- );
205
+ call.resolve();
206
+ });
127
207
  }
128
208
 
129
209
  @PluginMethod
@@ -151,7 +231,9 @@ public class CameraPreview
151
231
 
152
232
  @PluginMethod
153
233
  public void getAvailableDevices(PluginCall call) {
154
- List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
234
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
235
+ getContext()
236
+ );
155
237
  JSArray devicesArray = new JSArray();
156
238
  for (CameraDevice device : devices) {
157
239
  JSObject deviceJson = new JSObject();
@@ -198,14 +280,44 @@ public class CameraPreview
198
280
  call.reject("level parameter is required");
199
281
  return;
200
282
  }
283
+ Boolean autoFocus = call.getBoolean("autoFocus", true);
201
284
  try {
202
- cameraXView.setZoom(level);
285
+ cameraXView.setZoom(level, autoFocus);
203
286
  call.resolve();
204
287
  } catch (Exception e) {
205
288
  call.reject("Failed to set zoom: " + e.getMessage());
206
289
  }
207
290
  }
208
291
 
292
+ @PluginMethod
293
+ public void setFocus(PluginCall call) {
294
+ if (cameraXView == null || !cameraXView.isRunning()) {
295
+ call.reject("Camera is not running");
296
+ return;
297
+ }
298
+ Float x = call.getFloat("x");
299
+ Float y = call.getFloat("y");
300
+ if (x == null || y == null) {
301
+ call.reject("x and y parameters are required");
302
+ return;
303
+ }
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
+ }
309
+
310
+ getActivity()
311
+ .runOnUiThread(() -> {
312
+ try {
313
+ cameraXView.setFocus(x, y);
314
+ call.resolve();
315
+ } catch (Exception e) {
316
+ call.reject("Failed to set focus: " + e.getMessage());
317
+ }
318
+ });
319
+ }
320
+
209
321
  @PluginMethod
210
322
  public void setDeviceId(PluginCall call) {
211
323
  String deviceId = call.getString("deviceId");
@@ -228,7 +340,7 @@ public class CameraPreview
228
340
  JSObject rear = new JSObject();
229
341
  rear.put("facing", "rear");
230
342
  JSArray rearSizesJs = new JSArray();
231
- for(Size size : rearSizes) {
343
+ for (Size size : rearSizes) {
232
344
  JSObject sizeJs = new JSObject();
233
345
  sizeJs.put("width", size.getWidth());
234
346
  sizeJs.put("height", size.getHeight());
@@ -236,12 +348,12 @@ public class CameraPreview
236
348
  }
237
349
  rear.put("supportedPictureSizes", rearSizesJs);
238
350
  supportedPictureSizesResult.put(rear);
239
-
351
+
240
352
  List<Size> frontSizes = CameraXView.getSupportedPictureSizes("front");
241
353
  JSObject front = new JSObject();
242
354
  front.put("facing", "front");
243
355
  JSArray frontSizesJs = new JSArray();
244
- for(Size size : frontSizes) {
356
+ for (Size size : frontSizes) {
245
357
  JSObject sizeJs = new JSObject();
246
358
  sizeJs.put("width", size.getWidth());
247
359
  sizeJs.put("height", size.getHeight());
@@ -249,7 +361,7 @@ public class CameraPreview
249
361
  }
250
362
  front.put("supportedPictureSizes", frontSizesJs);
251
363
  supportedPictureSizesResult.put(front);
252
-
364
+
253
365
  JSObject ret = new JSObject();
254
366
  ret.put("supportedPictureSizes", supportedPictureSizesResult);
255
367
  call.resolve(ret);
@@ -308,8 +420,14 @@ public class CameraPreview
308
420
 
309
421
  @PermissionCallback
310
422
  private void handleCameraPermissionResult(PluginCall call) {
311
- if (PermissionState.GRANTED.equals(getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)) ||
312
- PermissionState.GRANTED.equals(getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS))) {
423
+ if (
424
+ PermissionState.GRANTED.equals(
425
+ getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)
426
+ ) ||
427
+ PermissionState.GRANTED.equals(
428
+ getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS)
429
+ )
430
+ ) {
313
431
  startCamera(call);
314
432
  } else {
315
433
  call.reject("Permission failed");
@@ -321,65 +439,384 @@ public class CameraPreview
321
439
  String originalDeviceId = call.getString("deviceId");
322
440
  String deviceId = originalDeviceId; // Use a mutable variable
323
441
 
324
- final String position = (positionParam == null || positionParam.isEmpty() || "rear".equals(positionParam) || "back".equals(positionParam)) ? "back" : "front";
325
- final int x = call.getInt("x", 0);
326
- final int y = call.getInt("y", 0);
442
+ final String position = (positionParam == null ||
443
+ positionParam.isEmpty() ||
444
+ "rear".equals(positionParam) ||
445
+ "back".equals(positionParam))
446
+ ? "back"
447
+ : "front";
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
+ );
327
472
  final int width = call.getInt("width", 0);
328
473
  final int height = call.getInt("height", 0);
329
474
  final int paddingBottom = call.getInt("paddingBottom", 0);
330
475
  final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
331
- final boolean storeToFile = Boolean.TRUE.equals(call.getBoolean("storeToFile", false));
332
- final boolean enableOpacity = Boolean.TRUE.equals(call.getBoolean("enableOpacity", false));
333
- final boolean enableZoom = Boolean.TRUE.equals(call.getBoolean("enableZoom", false));
334
- final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
335
- final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
336
- final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
337
-
338
- float targetZoom = 1.0f;
476
+ final boolean storeToFile = Boolean.TRUE.equals(
477
+ call.getBoolean("storeToFile", false)
478
+ );
479
+ final boolean enableOpacity = Boolean.TRUE.equals(
480
+ call.getBoolean("enableOpacity", false)
481
+ );
482
+ final boolean enableZoom = Boolean.TRUE.equals(
483
+ call.getBoolean("enableZoom", false)
484
+ );
485
+ final boolean disableExifHeaderStripping = Boolean.TRUE.equals(
486
+ call.getBoolean("disableExifHeaderStripping", false)
487
+ );
488
+ final boolean lockOrientation = Boolean.TRUE.equals(
489
+ call.getBoolean("lockAndroidOrientation", false)
490
+ );
491
+ final boolean disableAudio = Boolean.TRUE.equals(
492
+ call.getBoolean("disableAudio", true)
493
+ );
494
+ final String aspectRatio = call.getString("aspectRatio", "4:3");
495
+ final String gridMode = call.getString("gridMode", "none");
496
+ final float initialZoomLevel = call.getFloat("initialZoomLevel", 1.0f);
497
+
498
+ // Check for conflict between aspectRatio and size
499
+ if (
500
+ call.getData().has("aspectRatio") &&
501
+ (call.getData().has("width") || call.getData().has("height"))
502
+ ) {
503
+ call.reject(
504
+ "Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."
505
+ );
506
+ return;
507
+ }
508
+
509
+ float targetZoom = initialZoomLevel;
339
510
  // Check if the selected device is a physical ultra-wide
340
511
  if (originalDeviceId != null) {
341
- List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
342
- for (CameraDevice device : devices) {
343
- if (originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()) {
344
- for (LensInfo lens : device.getLenses()) {
345
- if ("ultraWide".equals(lens.getDeviceType())) {
346
- Log.d("CameraPreview", "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera.");
347
- targetZoom = 0.5f;
348
- // Force the use of the logical camera by clearing the specific deviceId
349
- deviceId = null;
350
- break;
351
- }
352
- }
512
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
513
+ getContext()
514
+ );
515
+ for (CameraDevice device : devices) {
516
+ if (
517
+ originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()
518
+ ) {
519
+ for (LensInfo lens : device.getLenses()) {
520
+ if ("ultraWide".equals(lens.getDeviceType())) {
521
+ Log.d(
522
+ "CameraPreview",
523
+ "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera."
524
+ );
525
+ targetZoom = 0.5f;
526
+ // Force the use of the logical camera by clearing the specific deviceId
527
+ deviceId = null;
528
+ break;
353
529
  }
354
- if (deviceId == null) break; // Exit outer loop once we've made our decision
530
+ }
355
531
  }
532
+ if (deviceId == null) break; // Exit outer loop once we've made our decision
533
+ }
356
534
  }
357
535
 
358
- previousOrientationRequest = getBridge().getActivity().getRequestedOrientation();
536
+ previousOrientationRequest = getBridge()
537
+ .getActivity()
538
+ .getRequestedOrientation();
359
539
  cameraXView = new CameraXView(getContext(), getBridge().getWebView());
360
540
  cameraXView.setListener(this);
361
541
 
362
542
  String finalDeviceId = deviceId;
363
543
  float finalTargetZoom = targetZoom;
364
- getBridge().getActivity().runOnUiThread(() -> {
365
- DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
544
+ getBridge()
545
+ .getActivity()
546
+ .runOnUiThread(() -> {
547
+ DisplayMetrics metrics = getBridge()
548
+ .getActivity()
549
+ .getResources()
550
+ .getDisplayMetrics();
366
551
  if (lockOrientation) {
367
- getBridge().getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
552
+ getBridge()
553
+ .getActivity()
554
+ .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
368
555
  }
369
- int computedX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, x, metrics);
370
- int computedY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, y, metrics);
371
- int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : getBridge().getWebView().getWidth();
372
- int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : getBridge().getWebView().getHeight();
373
- computedHeight -= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, metrics);
374
556
 
375
- CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
376
- config.setTargetZoom(finalTargetZoom);
557
+ // Debug: Let's check all the positioning information
558
+ ViewGroup webViewParent = (ViewGroup) getBridge()
559
+ .getWebView()
560
+ .getParent();
561
+
562
+ // Get webview position in different coordinate systems
563
+ int[] webViewLocationInWindow = new int[2];
564
+ int[] webViewLocationOnScreen = new int[2];
565
+ getBridge().getWebView().getLocationInWindow(webViewLocationInWindow);
566
+ getBridge().getWebView().getLocationOnScreen(webViewLocationOnScreen);
567
+
568
+ int webViewLeft = getBridge().getWebView().getLeft();
569
+ int webViewTop = getBridge().getWebView().getTop();
570
+
571
+ // Check parent position too
572
+ int[] parentLocationInWindow = new int[2];
573
+ int[] parentLocationOnScreen = new int[2];
574
+ webViewParent.getLocationInWindow(parentLocationInWindow);
575
+ webViewParent.getLocationOnScreen(parentLocationOnScreen);
576
+
577
+ // Calculate pixel ratio
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());
377
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);
627
+
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
+ }
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
+ );
735
+ Log.d("CameraPreview", "=== COORDINATE DEBUG ===");
736
+ Log.d(
737
+ "CameraPreview",
738
+ "WebView getLeft/getTop: (" + webViewLeft + ", " + webViewTop + ")"
739
+ );
740
+ Log.d(
741
+ "CameraPreview",
742
+ "WebView locationInWindow: (" +
743
+ webViewLocationInWindow[0] +
744
+ ", " +
745
+ webViewLocationInWindow[1] +
746
+ ")"
747
+ );
748
+ Log.d(
749
+ "CameraPreview",
750
+ "WebView locationOnScreen: (" +
751
+ webViewLocationOnScreen[0] +
752
+ ", " +
753
+ webViewLocationOnScreen[1] +
754
+ ")"
755
+ );
756
+ Log.d(
757
+ "CameraPreview",
758
+ "Parent locationInWindow: (" +
759
+ parentLocationInWindow[0] +
760
+ ", " +
761
+ parentLocationInWindow[1] +
762
+ ")"
763
+ );
764
+ Log.d(
765
+ "CameraPreview",
766
+ "Parent locationOnScreen: (" +
767
+ parentLocationOnScreen[0] +
768
+ ", " +
769
+ parentLocationOnScreen[1] +
770
+ ")"
771
+ );
772
+ Log.d(
773
+ "CameraPreview",
774
+ "Parent class: " + webViewParent.getClass().getSimpleName()
775
+ );
776
+ Log.d(
777
+ "CameraPreview",
778
+ "Requested position (logical): (" + x + ", " + y + ")"
779
+ );
780
+ Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
781
+ Log.d(
782
+ "CameraPreview",
783
+ "Final computed position (no offset): (" +
784
+ computedX +
785
+ ", " +
786
+ computedY +
787
+ ")"
788
+ );
789
+ Log.d("CameraPreview", "5. IS_CENTERED - " + (x == -1 || y == -1));
790
+ Log.d("CameraPreview", "========================");
791
+
792
+ // Pass along whether we're centering so CameraXView knows not to add insets
793
+ boolean isCentered = (x == -1 || y == -1);
794
+
795
+ CameraSessionConfiguration config = new CameraSessionConfiguration(
796
+ finalDeviceId,
797
+ position,
798
+ computedX,
799
+ computedY,
800
+ computedWidth,
801
+ computedHeight,
802
+ paddingBottom,
803
+ toBack,
804
+ storeToFile,
805
+ enableOpacity,
806
+ enableZoom,
807
+ disableExifHeaderStripping,
808
+ disableAudio,
809
+ 1.0f,
810
+ aspectRatio,
811
+ gridMode
812
+ );
813
+ config.setTargetZoom(finalTargetZoom);
814
+ config.setCentered(isCentered);
815
+
378
816
  bridge.saveCall(call);
379
817
  cameraStartCallbackId = call.getCallbackId();
380
818
  cameraXView.startSession(config);
381
- }
382
- );
819
+ });
383
820
  }
384
821
 
385
822
  @Override
@@ -407,34 +844,245 @@ public class CameraPreview
407
844
  bridge.releaseCall(pluginCall);
408
845
  }
409
846
 
847
+ @Override
848
+ public void onCameraStarted(int width, int height, int x, int y) {
849
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
850
+ if (call != null) {
851
+ // Convert pixel values back to logical units
852
+ DisplayMetrics metrics = getBridge()
853
+ .getActivity()
854
+ .getResources()
855
+ .getDisplayMetrics();
856
+ float pixelRatio = metrics.density;
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
+
915
+ JSObject result = new JSObject();
916
+ result.put("width", width / pixelRatio);
917
+ result.put("height", height / pixelRatio);
918
+ result.put("x", x / pixelRatio);
919
+ result.put("y", relativeY / pixelRatio);
920
+ call.resolve(result);
921
+ bridge.releaseCall(call);
922
+ cameraStartCallbackId = null; // Prevent re-use
923
+ }
924
+ }
925
+
410
926
  @Override
411
927
  public void onSampleTaken(String result) {
412
- JSObject jsObject = new JSObject();
413
- jsObject.put("value", result);
414
- bridge.getSavedCall(snapshotCallbackId).resolve(jsObject);
928
+ // Handle sample taken if needed
929
+ Log.i("CameraPreview", "Sample taken: " + result);
415
930
  }
416
931
 
417
932
  @Override
418
933
  public void onSampleTakenError(String message) {
419
- bridge.getSavedCall(snapshotCallbackId).reject(message);
934
+ // Handle sample taken error if needed
935
+ Log.e("CameraPreview", "Sample taken error: " + message);
420
936
  }
421
937
 
422
938
  @Override
423
- public void onCameraStarted() {
424
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
425
- if (pluginCall != null) {
426
- pluginCall.resolve();
427
- bridge.releaseCall(pluginCall);
939
+ public void onCameraStartError(String message) {
940
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
941
+ if (call != null) {
942
+ call.reject(message);
943
+ bridge.releaseCall(call);
944
+ cameraStartCallbackId = null;
428
945
  }
429
946
  }
430
947
 
431
- @Override
432
- public void onCameraStartError(String message) {
433
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
434
- if (pluginCall != null) {
435
- pluginCall.reject(message);
436
- bridge.releaseCall(pluginCall);
948
+ @PluginMethod
949
+ public void setAspectRatio(PluginCall call) {
950
+ if (cameraXView == null || !cameraXView.isRunning()) {
951
+ call.reject("Camera is not running");
952
+ return;
953
+ }
954
+ String aspectRatio = call.getString("aspectRatio", "4:3");
955
+ Float x = call.getFloat("x");
956
+ Float y = call.getFloat("y");
957
+
958
+ getActivity()
959
+ .runOnUiThread(() -> {
960
+ cameraXView.setAspectRatio(aspectRatio, x, y, () -> {
961
+ // Return the actual preview bounds after layout and camera operations are complete
962
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
963
+ JSObject ret = new JSObject();
964
+ ret.put("x", bounds[0]);
965
+ ret.put("y", bounds[1]);
966
+ ret.put("width", bounds[2]);
967
+ ret.put("height", bounds[3]);
968
+ call.resolve(ret);
969
+ });
970
+ });
971
+ }
972
+
973
+ @PluginMethod
974
+ public void getAspectRatio(PluginCall call) {
975
+ if (cameraXView == null || !cameraXView.isRunning()) {
976
+ call.reject("Camera is not running");
977
+ return;
437
978
  }
979
+ String aspectRatio = cameraXView.getAspectRatio();
980
+ JSObject ret = new JSObject();
981
+ ret.put("aspectRatio", aspectRatio);
982
+ call.resolve(ret);
983
+ }
984
+
985
+ @PluginMethod
986
+ public void setGridMode(PluginCall call) {
987
+ if (cameraXView == null || !cameraXView.isRunning()) {
988
+ call.reject("Camera is not running");
989
+ return;
990
+ }
991
+ String gridMode = call.getString("gridMode", "none");
992
+ getActivity()
993
+ .runOnUiThread(() -> {
994
+ cameraXView.setGridMode(gridMode);
995
+ call.resolve();
996
+ });
438
997
  }
439
998
 
999
+ @PluginMethod
1000
+ public void getGridMode(PluginCall call) {
1001
+ if (cameraXView == null || !cameraXView.isRunning()) {
1002
+ call.reject("Camera is not running");
1003
+ return;
1004
+ }
1005
+ JSObject ret = new JSObject();
1006
+ ret.put("gridMode", cameraXView.getGridMode());
1007
+ call.resolve(ret);
1008
+ }
1009
+
1010
+ @PluginMethod
1011
+ public void getPreviewSize(PluginCall call) {
1012
+ if (cameraXView == null || !cameraXView.isRunning()) {
1013
+ call.reject("Camera is not running");
1014
+ return;
1015
+ }
1016
+
1017
+ // Convert pixel values back to logical units
1018
+ DisplayMetrics metrics = getBridge()
1019
+ .getActivity()
1020
+ .getResources()
1021
+ .getDisplayMetrics();
1022
+ float pixelRatio = metrics.density;
1023
+
1024
+ JSObject ret = new JSObject();
1025
+ ret.put("x", cameraXView.getPreviewX() / pixelRatio);
1026
+ ret.put("y", cameraXView.getPreviewY() / pixelRatio);
1027
+ ret.put("width", cameraXView.getPreviewWidth() / pixelRatio);
1028
+ ret.put("height", cameraXView.getPreviewHeight() / pixelRatio);
1029
+ call.resolve(ret);
1030
+ }
1031
+
1032
+ @PluginMethod
1033
+ public void setPreviewSize(PluginCall call) {
1034
+ if (cameraXView == null || !cameraXView.isRunning()) {
1035
+ call.reject("Camera is not running");
1036
+ return;
1037
+ }
1038
+
1039
+ // Get values from call - null values will become 0
1040
+ Integer xParam = call.getInt("x");
1041
+ Integer yParam = call.getInt("y");
1042
+ Integer widthParam = call.getInt("width");
1043
+ Integer heightParam = call.getInt("height");
1044
+
1045
+ // Apply pixel ratio conversion to non-null values
1046
+ DisplayMetrics metrics = getBridge()
1047
+ .getActivity()
1048
+ .getResources()
1049
+ .getDisplayMetrics();
1050
+ float pixelRatio = metrics.density;
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
+
1063
+ int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
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
+ }
1070
+ int width = (widthParam != null && widthParam > 0)
1071
+ ? (int) (widthParam * pixelRatio)
1072
+ : 0;
1073
+ int height = (heightParam != null && heightParam > 0)
1074
+ ? (int) (heightParam * pixelRatio)
1075
+ : 0;
1076
+
1077
+ cameraXView.setPreviewSize(x, y, width, height, () -> {
1078
+ // Return the actual preview bounds after layout operations are complete
1079
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
1080
+ JSObject ret = new JSObject();
1081
+ ret.put("x", bounds[0]);
1082
+ ret.put("y", bounds[1]);
1083
+ ret.put("width", bounds[2]);
1084
+ ret.put("height", bounds[3]);
1085
+ call.resolve(ret);
1086
+ });
1087
+ }
440
1088
  }