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

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 +218 -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 +759 -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 +333 -29
  23. package/dist/esm/definitions.d.ts +156 -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 +592 -95
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +590 -95
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +590 -95
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Sources/CapgoCameraPreview/CameraController.swift +907 -222
  33. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  34. package/ios/Sources/CapgoCameraPreview/Plugin.swift +986 -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,412 @@ 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 String positioning = call.getString("positioning", "center");
497
+ final float initialZoomLevel = call.getFloat("initialZoomLevel", 1.0f);
498
+
499
+ // Check for conflict between aspectRatio and size
500
+ if (
501
+ call.getData().has("aspectRatio") &&
502
+ (call.getData().has("width") || call.getData().has("height"))
503
+ ) {
504
+ call.reject(
505
+ "Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."
506
+ );
507
+ return;
508
+ }
509
+
510
+ float targetZoom = initialZoomLevel;
339
511
  // Check if the selected device is a physical ultra-wide
340
512
  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
- }
513
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
514
+ getContext()
515
+ );
516
+ for (CameraDevice device : devices) {
517
+ if (
518
+ originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()
519
+ ) {
520
+ for (LensInfo lens : device.getLenses()) {
521
+ if ("ultraWide".equals(lens.getDeviceType())) {
522
+ Log.d(
523
+ "CameraPreview",
524
+ "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera."
525
+ );
526
+ targetZoom = 0.5f;
527
+ // Force the use of the logical camera by clearing the specific deviceId
528
+ deviceId = null;
529
+ break;
353
530
  }
354
- if (deviceId == null) break; // Exit outer loop once we've made our decision
531
+ }
355
532
  }
533
+ if (deviceId == null) break; // Exit outer loop once we've made our decision
534
+ }
356
535
  }
357
536
 
358
- previousOrientationRequest = getBridge().getActivity().getRequestedOrientation();
537
+ previousOrientationRequest = getBridge()
538
+ .getActivity()
539
+ .getRequestedOrientation();
359
540
  cameraXView = new CameraXView(getContext(), getBridge().getWebView());
360
541
  cameraXView.setListener(this);
361
542
 
362
543
  String finalDeviceId = deviceId;
363
544
  float finalTargetZoom = targetZoom;
364
- getBridge().getActivity().runOnUiThread(() -> {
365
- DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
545
+ getBridge()
546
+ .getActivity()
547
+ .runOnUiThread(() -> {
548
+ DisplayMetrics metrics = getBridge()
549
+ .getActivity()
550
+ .getResources()
551
+ .getDisplayMetrics();
366
552
  if (lockOrientation) {
367
- getBridge().getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
553
+ getBridge()
554
+ .getActivity()
555
+ .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
368
556
  }
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
557
 
375
- CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
376
- config.setTargetZoom(finalTargetZoom);
558
+ // Debug: Let's check all the positioning information
559
+ ViewGroup webViewParent = (ViewGroup) getBridge()
560
+ .getWebView()
561
+ .getParent();
562
+
563
+ // Get webview position in different coordinate systems
564
+ int[] webViewLocationInWindow = new int[2];
565
+ int[] webViewLocationOnScreen = new int[2];
566
+ getBridge().getWebView().getLocationInWindow(webViewLocationInWindow);
567
+ getBridge().getWebView().getLocationOnScreen(webViewLocationOnScreen);
568
+
569
+ int webViewLeft = getBridge().getWebView().getLeft();
570
+ int webViewTop = getBridge().getWebView().getTop();
571
+
572
+ // Check parent position too
573
+ int[] parentLocationInWindow = new int[2];
574
+ int[] parentLocationOnScreen = new int[2];
575
+ webViewParent.getLocationInWindow(parentLocationInWindow);
576
+ webViewParent.getLocationOnScreen(parentLocationOnScreen);
577
+
578
+ // Calculate pixel ratio
579
+ float pixelRatio = metrics.density;
580
+
581
+ // The key insight: JavaScript coordinates are relative to the WebView's viewport
582
+ // If the WebView is positioned below the status bar (webViewLocationOnScreen[1] > 0),
583
+ // we need to add that offset when placing native views
584
+ int webViewTopInset = webViewLocationOnScreen[1];
585
+ boolean isEdgeToEdgeActive = webViewLocationOnScreen[1] > 0;
586
+
587
+ // Log all the positioning information for debugging
588
+ Log.d("CameraPreview", "WebView Position Debug:");
589
+ Log.d("CameraPreview", " - webView.getTop(): " + webViewTop);
590
+ Log.d("CameraPreview", " - webView.getLeft(): " + webViewLeft);
591
+ Log.d("CameraPreview", " - webView locationInWindow: (" + webViewLocationInWindow[0] + ", " + webViewLocationInWindow[1] + ")");
592
+ Log.d("CameraPreview", " - webView locationOnScreen: (" + webViewLocationOnScreen[0] + ", " + webViewLocationOnScreen[1] + ")");
593
+ Log.d("CameraPreview", " - parent locationInWindow: (" + parentLocationInWindow[0] + ", " + parentLocationInWindow[1] + ")");
594
+ Log.d("CameraPreview", " - parent locationOnScreen: (" + parentLocationOnScreen[0] + ", " + parentLocationOnScreen[1] + ")");
595
+
596
+ // Check if WebView has margins
597
+ View webView = getBridge().getWebView();
598
+ ViewGroup.LayoutParams webViewLayoutParams = webView.getLayoutParams();
599
+ if (webViewLayoutParams instanceof ViewGroup.MarginLayoutParams) {
600
+ ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) webViewLayoutParams;
601
+ Log.d("CameraPreview", " - webView margins: left=" + marginParams.leftMargin +
602
+ ", top=" + marginParams.topMargin +
603
+ ", right=" + marginParams.rightMargin +
604
+ ", bottom=" + marginParams.bottomMargin);
605
+ }
606
+
607
+ // Check WebView padding
608
+ Log.d("CameraPreview", " - webView padding: left=" + webView.getPaddingLeft() +
609
+ ", top=" + webView.getPaddingTop() +
610
+ ", right=" + webView.getPaddingRight() +
611
+ ", bottom=" + webView.getPaddingBottom());
377
612
 
613
+ Log.d("CameraPreview", " - Using webViewTopInset: " + webViewTopInset);
614
+ Log.d("CameraPreview", " - isEdgeToEdgeActive: " + isEdgeToEdgeActive);
615
+
616
+ // Calculate position - center if x or y is -1
617
+ int computedX;
618
+ int computedY;
619
+
620
+ // Calculate dimensions first
621
+ int computedWidth = width != 0
622
+ ? (int) (width * pixelRatio)
623
+ : getBridge().getWebView().getWidth();
624
+ int computedHeight = height != 0
625
+ ? (int) (height * pixelRatio)
626
+ : getBridge().getWebView().getHeight();
627
+ computedHeight -= (int) (paddingBottom * pixelRatio);
628
+
629
+ Log.d("CameraPreview", "========================");
630
+ Log.d("CameraPreview", "POSITIONING CALCULATIONS:");
631
+ Log.d("CameraPreview", "1. INPUT - x: " + x + ", y: " + y + ", width: " + width + ", height: " + height);
632
+ Log.d("CameraPreview", "2. PIXEL RATIO: " + pixelRatio);
633
+ Log.d("CameraPreview", "3. SCREEN - width: " + metrics.widthPixels + ", height: " + metrics.heightPixels);
634
+ Log.d("CameraPreview", "4. WEBVIEW - width: " + getBridge().getWebView().getWidth() + ", height: " + getBridge().getWebView().getHeight());
635
+ Log.d("CameraPreview", "5. COMPUTED DIMENSIONS - width: " + computedWidth + ", height: " + computedHeight);
636
+
637
+ if (x == -1) {
638
+ // Center horizontally
639
+ int screenWidth = metrics.widthPixels;
640
+ computedX = (screenWidth - computedWidth) / 2;
641
+ Log.d(
642
+ "CameraPreview",
643
+ "Centering horizontally: screenWidth=" +
644
+ screenWidth +
645
+ ", computedWidth=" +
646
+ computedWidth +
647
+ ", computedX=" +
648
+ computedX
649
+ );
650
+ } else {
651
+ computedX = (int) (x * pixelRatio);
652
+ Log.d(
653
+ "CameraPreview",
654
+ "Using provided X position: " +
655
+ x +
656
+ " * " +
657
+ pixelRatio +
658
+ " = " +
659
+ computedX
660
+ );
661
+ }
662
+
663
+ if (y == -1) {
664
+ // Position vertically based on positioning parameter
665
+ int screenHeight = metrics.heightPixels;
666
+
667
+ switch (positioning) {
668
+ case "top":
669
+ computedY = 0;
670
+ Log.d(
671
+ "CameraPreview",
672
+ "Positioning at top: computedY=0"
673
+ );
674
+ break;
675
+ case "bottom":
676
+ computedY = screenHeight - computedHeight;
677
+ Log.d(
678
+ "CameraPreview",
679
+ "Positioning at bottom: screenHeight=" +
680
+ screenHeight +
681
+ ", computedHeight=" +
682
+ computedHeight +
683
+ ", computedY=" +
684
+ computedY
685
+ );
686
+ break;
687
+ case "center":
688
+ default:
689
+ // Center vertically
690
+ if (isEdgeToEdgeActive) {
691
+ // When WebView is offset from top, center within the available space
692
+ // The camera should be centered in the full screen, not just the WebView area
693
+ computedY = (screenHeight - computedHeight) / 2;
694
+ Log.d(
695
+ "CameraPreview",
696
+ "Centering vertically with WebView offset: screenHeight=" +
697
+ screenHeight +
698
+ ", webViewTop=" +
699
+ webViewTopInset +
700
+ ", computedHeight=" +
701
+ computedHeight +
702
+ ", computedY=" +
703
+ computedY
704
+ );
705
+ } else {
706
+ // Normal mode - use full screen height
707
+ computedY = (screenHeight - computedHeight) / 2;
708
+ Log.d(
709
+ "CameraPreview",
710
+ "Centering vertically (normal): screenHeight=" +
711
+ screenHeight +
712
+ ", computedHeight=" +
713
+ computedHeight +
714
+ ", computedY=" +
715
+ computedY
716
+ );
717
+ }
718
+ break;
719
+ }
720
+ } else {
721
+ computedY = (int) (y * pixelRatio);
722
+ // If edge-to-edge is active, JavaScript Y is relative to WebView content area
723
+ // We need to add the inset to get absolute screen position
724
+ if (isEdgeToEdgeActive) {
725
+ computedY += webViewTopInset;
726
+ Log.d(
727
+ "CameraPreview",
728
+ "Edge-to-edge adjustment: Y position " +
729
+ (int)(y * pixelRatio) +
730
+ " + inset " +
731
+ webViewTopInset +
732
+ " = " +
733
+ computedY
734
+ );
735
+ }
736
+ Log.d(
737
+ "CameraPreview",
738
+ "Using provided Y position: " +
739
+ y +
740
+ " * " +
741
+ pixelRatio +
742
+ " = " +
743
+ computedY +
744
+ (isEdgeToEdgeActive ? " (adjusted for edge-to-edge)" : "")
745
+ );
746
+ }
747
+
748
+ Log.d(
749
+ "CameraPreview",
750
+ "2b. EDGE-TO-EDGE - " + (isEdgeToEdgeActive ? "ACTIVE (inset=" + webViewTopInset + ")" : "INACTIVE")
751
+ );
752
+ Log.d(
753
+ "CameraPreview",
754
+ "3. COMPUTED POSITION - x=" + computedX + ", y=" + computedY
755
+ );
756
+ Log.d(
757
+ "CameraPreview",
758
+ "4. COMPUTED SIZE - width=" +
759
+ computedWidth +
760
+ ", height=" +
761
+ computedHeight
762
+ );
763
+ Log.d("CameraPreview", "=== COORDINATE DEBUG ===");
764
+ Log.d(
765
+ "CameraPreview",
766
+ "WebView getLeft/getTop: (" + webViewLeft + ", " + webViewTop + ")"
767
+ );
768
+ Log.d(
769
+ "CameraPreview",
770
+ "WebView locationInWindow: (" +
771
+ webViewLocationInWindow[0] +
772
+ ", " +
773
+ webViewLocationInWindow[1] +
774
+ ")"
775
+ );
776
+ Log.d(
777
+ "CameraPreview",
778
+ "WebView locationOnScreen: (" +
779
+ webViewLocationOnScreen[0] +
780
+ ", " +
781
+ webViewLocationOnScreen[1] +
782
+ ")"
783
+ );
784
+ Log.d(
785
+ "CameraPreview",
786
+ "Parent locationInWindow: (" +
787
+ parentLocationInWindow[0] +
788
+ ", " +
789
+ parentLocationInWindow[1] +
790
+ ")"
791
+ );
792
+ Log.d(
793
+ "CameraPreview",
794
+ "Parent locationOnScreen: (" +
795
+ parentLocationOnScreen[0] +
796
+ ", " +
797
+ parentLocationOnScreen[1] +
798
+ ")"
799
+ );
800
+ Log.d(
801
+ "CameraPreview",
802
+ "Parent class: " + webViewParent.getClass().getSimpleName()
803
+ );
804
+ Log.d(
805
+ "CameraPreview",
806
+ "Requested position (logical): (" + x + ", " + y + ")"
807
+ );
808
+ Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
809
+ Log.d(
810
+ "CameraPreview",
811
+ "Final computed position (no offset): (" +
812
+ computedX +
813
+ ", " +
814
+ computedY +
815
+ ")"
816
+ );
817
+ Log.d("CameraPreview", "5. IS_CENTERED - " + (x == -1 || y == -1));
818
+ Log.d("CameraPreview", "========================");
819
+
820
+ // Pass along whether we're centering so CameraXView knows not to add insets
821
+ boolean isCentered = (x == -1 || y == -1);
822
+
823
+ CameraSessionConfiguration config = new CameraSessionConfiguration(
824
+ finalDeviceId,
825
+ position,
826
+ computedX,
827
+ computedY,
828
+ computedWidth,
829
+ computedHeight,
830
+ paddingBottom,
831
+ toBack,
832
+ storeToFile,
833
+ enableOpacity,
834
+ enableZoom,
835
+ disableExifHeaderStripping,
836
+ disableAudio,
837
+ 1.0f,
838
+ aspectRatio,
839
+ gridMode
840
+ );
841
+ config.setTargetZoom(finalTargetZoom);
842
+ config.setCentered(isCentered);
843
+
378
844
  bridge.saveCall(call);
379
845
  cameraStartCallbackId = call.getCallbackId();
380
846
  cameraXView.startSession(config);
381
- }
382
- );
847
+ });
383
848
  }
384
849
 
385
850
  @Override
@@ -407,34 +872,245 @@ public class CameraPreview
407
872
  bridge.releaseCall(pluginCall);
408
873
  }
409
874
 
875
+ @Override
876
+ public void onCameraStarted(int width, int height, int x, int y) {
877
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
878
+ if (call != null) {
879
+ // Convert pixel values back to logical units
880
+ DisplayMetrics metrics = getBridge()
881
+ .getActivity()
882
+ .getResources()
883
+ .getDisplayMetrics();
884
+ float pixelRatio = metrics.density;
885
+
886
+ // When WebView is offset from the top (e.g., below status bar),
887
+ // we need to convert between JavaScript coordinates (relative to WebView)
888
+ // and native coordinates (relative to screen)
889
+ WebView webView = getBridge().getWebView();
890
+ int webViewTopInset = 0;
891
+ boolean isEdgeToEdgeActive = false;
892
+ if (webView != null) {
893
+ int[] location = new int[2];
894
+ webView.getLocationOnScreen(location);
895
+ webViewTopInset = location[1];
896
+ isEdgeToEdgeActive = webViewTopInset > 0;
897
+ }
898
+
899
+ // Only convert to relative position if edge-to-edge is active
900
+ int relativeY = isEdgeToEdgeActive ? (y - webViewTopInset) : y;
901
+
902
+ Log.d("CameraPreview", "========================");
903
+ Log.d("CameraPreview", "CAMERA STARTED - POSITION RETURNED:");
904
+ Log.d(
905
+ "CameraPreview",
906
+ "7. RETURNED (pixels) - x=" +
907
+ x +
908
+ ", y=" +
909
+ y +
910
+ ", width=" +
911
+ width +
912
+ ", height=" +
913
+ height
914
+ );
915
+ Log.d(
916
+ "CameraPreview",
917
+ "8. EDGE-TO-EDGE - " + (isEdgeToEdgeActive ? "ACTIVE" : "INACTIVE")
918
+ );
919
+ Log.d("CameraPreview", "9. WEBVIEW INSET - " + webViewTopInset);
920
+ Log.d(
921
+ "CameraPreview",
922
+ "10. RELATIVE Y - " +
923
+ relativeY +
924
+ " (y=" +
925
+ y +
926
+ (isEdgeToEdgeActive ? " - inset=" + webViewTopInset : " unchanged") +
927
+ ")"
928
+ );
929
+ Log.d(
930
+ "CameraPreview",
931
+ "11. RETURNED (logical) - x=" +
932
+ (x / pixelRatio) +
933
+ ", y=" +
934
+ (relativeY / pixelRatio) +
935
+ ", width=" +
936
+ (width / pixelRatio) +
937
+ ", height=" +
938
+ (height / pixelRatio)
939
+ );
940
+ Log.d("CameraPreview", "12. PIXEL RATIO - " + pixelRatio);
941
+ Log.d("CameraPreview", "========================");
942
+
943
+ JSObject result = new JSObject();
944
+ result.put("width", width / pixelRatio);
945
+ result.put("height", height / pixelRatio);
946
+ result.put("x", x / pixelRatio);
947
+ result.put("y", relativeY / pixelRatio);
948
+ call.resolve(result);
949
+ bridge.releaseCall(call);
950
+ cameraStartCallbackId = null; // Prevent re-use
951
+ }
952
+ }
953
+
410
954
  @Override
411
955
  public void onSampleTaken(String result) {
412
- JSObject jsObject = new JSObject();
413
- jsObject.put("value", result);
414
- bridge.getSavedCall(snapshotCallbackId).resolve(jsObject);
956
+ // Handle sample taken if needed
957
+ Log.i("CameraPreview", "Sample taken: " + result);
415
958
  }
416
959
 
417
960
  @Override
418
961
  public void onSampleTakenError(String message) {
419
- bridge.getSavedCall(snapshotCallbackId).reject(message);
962
+ // Handle sample taken error if needed
963
+ Log.e("CameraPreview", "Sample taken error: " + message);
420
964
  }
421
965
 
422
966
  @Override
423
- public void onCameraStarted() {
424
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
425
- if (pluginCall != null) {
426
- pluginCall.resolve();
427
- bridge.releaseCall(pluginCall);
967
+ public void onCameraStartError(String message) {
968
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
969
+ if (call != null) {
970
+ call.reject(message);
971
+ bridge.releaseCall(call);
972
+ cameraStartCallbackId = null;
428
973
  }
429
974
  }
430
975
 
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);
976
+ @PluginMethod
977
+ public void setAspectRatio(PluginCall call) {
978
+ if (cameraXView == null || !cameraXView.isRunning()) {
979
+ call.reject("Camera is not running");
980
+ return;
981
+ }
982
+ String aspectRatio = call.getString("aspectRatio", "4:3");
983
+ Float x = call.getFloat("x");
984
+ Float y = call.getFloat("y");
985
+
986
+ getActivity()
987
+ .runOnUiThread(() -> {
988
+ cameraXView.setAspectRatio(aspectRatio, x, y, () -> {
989
+ // Return the actual preview bounds after layout and camera operations are complete
990
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
991
+ JSObject ret = new JSObject();
992
+ ret.put("x", bounds[0]);
993
+ ret.put("y", bounds[1]);
994
+ ret.put("width", bounds[2]);
995
+ ret.put("height", bounds[3]);
996
+ call.resolve(ret);
997
+ });
998
+ });
999
+ }
1000
+
1001
+ @PluginMethod
1002
+ public void getAspectRatio(PluginCall call) {
1003
+ if (cameraXView == null || !cameraXView.isRunning()) {
1004
+ call.reject("Camera is not running");
1005
+ return;
437
1006
  }
1007
+ String aspectRatio = cameraXView.getAspectRatio();
1008
+ JSObject ret = new JSObject();
1009
+ ret.put("aspectRatio", aspectRatio);
1010
+ call.resolve(ret);
1011
+ }
1012
+
1013
+ @PluginMethod
1014
+ public void setGridMode(PluginCall call) {
1015
+ if (cameraXView == null || !cameraXView.isRunning()) {
1016
+ call.reject("Camera is not running");
1017
+ return;
1018
+ }
1019
+ String gridMode = call.getString("gridMode", "none");
1020
+ getActivity()
1021
+ .runOnUiThread(() -> {
1022
+ cameraXView.setGridMode(gridMode);
1023
+ call.resolve();
1024
+ });
438
1025
  }
439
1026
 
1027
+ @PluginMethod
1028
+ public void getGridMode(PluginCall call) {
1029
+ if (cameraXView == null || !cameraXView.isRunning()) {
1030
+ call.reject("Camera is not running");
1031
+ return;
1032
+ }
1033
+ JSObject ret = new JSObject();
1034
+ ret.put("gridMode", cameraXView.getGridMode());
1035
+ call.resolve(ret);
1036
+ }
1037
+
1038
+ @PluginMethod
1039
+ public void getPreviewSize(PluginCall call) {
1040
+ if (cameraXView == null || !cameraXView.isRunning()) {
1041
+ call.reject("Camera is not running");
1042
+ return;
1043
+ }
1044
+
1045
+ // Convert pixel values back to logical units
1046
+ DisplayMetrics metrics = getBridge()
1047
+ .getActivity()
1048
+ .getResources()
1049
+ .getDisplayMetrics();
1050
+ float pixelRatio = metrics.density;
1051
+
1052
+ JSObject ret = new JSObject();
1053
+ ret.put("x", cameraXView.getPreviewX() / pixelRatio);
1054
+ ret.put("y", cameraXView.getPreviewY() / pixelRatio);
1055
+ ret.put("width", cameraXView.getPreviewWidth() / pixelRatio);
1056
+ ret.put("height", cameraXView.getPreviewHeight() / pixelRatio);
1057
+ call.resolve(ret);
1058
+ }
1059
+
1060
+ @PluginMethod
1061
+ public void setPreviewSize(PluginCall call) {
1062
+ if (cameraXView == null || !cameraXView.isRunning()) {
1063
+ call.reject("Camera is not running");
1064
+ return;
1065
+ }
1066
+
1067
+ // Get values from call - null values will become 0
1068
+ Integer xParam = call.getInt("x");
1069
+ Integer yParam = call.getInt("y");
1070
+ Integer widthParam = call.getInt("width");
1071
+ Integer heightParam = call.getInt("height");
1072
+
1073
+ // Apply pixel ratio conversion to non-null values
1074
+ DisplayMetrics metrics = getBridge()
1075
+ .getActivity()
1076
+ .getResources()
1077
+ .getDisplayMetrics();
1078
+ float pixelRatio = metrics.density;
1079
+
1080
+ // Check if edge-to-edge mode is active
1081
+ WebView webView = getBridge().getWebView();
1082
+ int webViewTopInset = 0;
1083
+ boolean isEdgeToEdgeActive = false;
1084
+ if (webView != null) {
1085
+ int[] location = new int[2];
1086
+ webView.getLocationOnScreen(location);
1087
+ webViewTopInset = location[1];
1088
+ isEdgeToEdgeActive = webViewTopInset > 0;
1089
+ }
1090
+
1091
+ int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
1092
+ int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
1093
+
1094
+ // Add edge-to-edge inset to Y if active
1095
+ if (isEdgeToEdgeActive && y > 0) {
1096
+ y += webViewTopInset;
1097
+ }
1098
+ int width = (widthParam != null && widthParam > 0)
1099
+ ? (int) (widthParam * pixelRatio)
1100
+ : 0;
1101
+ int height = (heightParam != null && heightParam > 0)
1102
+ ? (int) (heightParam * pixelRatio)
1103
+ : 0;
1104
+
1105
+ cameraXView.setPreviewSize(x, y, width, height, () -> {
1106
+ // Return the actual preview bounds after layout operations are complete
1107
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
1108
+ JSObject ret = new JSObject();
1109
+ ret.put("x", bounds[0]);
1110
+ ret.put("y", bounds[1]);
1111
+ ret.put("width", bounds[2]);
1112
+ ret.put("height", bounds[3]);
1113
+ call.resolve(ret);
1114
+ });
1115
+ }
440
1116
  }