@capgo/camera-preview 7.3.12 → 7.4.0-beta.10

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 (61) hide show
  1. package/CapgoCameraPreview.podspec +16 -13
  2. package/README.md +467 -73
  3. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  4. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  7. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  8. package/android/.gradle/8.14.2/fileChanges/last-build.bin +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  10. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  11. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  12. package/android/.gradle/8.14.2/gc.properties +0 -0
  13. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  14. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  15. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  16. package/android/.gradle/file-system.probe +0 -0
  17. package/android/.gradle/vcs-1/gc.properties +0 -0
  18. package/android/build.gradle +11 -0
  19. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  20. package/android/src/main/AndroidManifest.xml +5 -3
  21. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +472 -541
  22. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +1648 -0
  23. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +82 -0
  24. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +54 -0
  25. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +70 -0
  26. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +79 -0
  27. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +34 -0
  28. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +34 -0
  29. package/dist/docs.json +934 -154
  30. package/dist/esm/definitions.d.ts +445 -83
  31. package/dist/esm/definitions.js +10 -1
  32. package/dist/esm/definitions.js.map +1 -1
  33. package/dist/esm/web.d.ts +73 -3
  34. package/dist/esm/web.js +492 -68
  35. package/dist/esm/web.js.map +1 -1
  36. package/dist/plugin.cjs.js +498 -68
  37. package/dist/plugin.cjs.js.map +1 -1
  38. package/dist/plugin.js +498 -68
  39. package/dist/plugin.js.map +1 -1
  40. package/ios/{Plugin → Sources/CapgoCameraPreview}/CameraController.swift +601 -59
  41. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  42. package/ios/Sources/CapgoCameraPreview/Plugin.swift +1369 -0
  43. package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
  44. package/package.json +1 -1
  45. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
  46. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
  47. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
  48. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
  49. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
  50. package/ios/Plugin/Info.plist +0 -24
  51. package/ios/Plugin/Plugin.h +0 -10
  52. package/ios/Plugin/Plugin.m +0 -18
  53. package/ios/Plugin/Plugin.swift +0 -511
  54. package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
  55. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  56. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
  57. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  58. package/ios/PluginTests/Info.plist +0 -22
  59. package/ios/PluginTests/PluginTests.swift +0 -83
  60. package/ios/Podfile +0 -13
  61. package/ios/Podfile.lock +0 -23
@@ -3,30 +3,11 @@ 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.app.FragmentManager;
7
- import android.app.FragmentTransaction;
8
- import android.content.Context;
6
+ import android.Manifest;
9
7
  import android.content.pm.ActivityInfo;
10
- import android.graphics.Color;
11
- import android.graphics.ImageFormat;
12
- import android.graphics.Point;
13
- import android.hardware.Camera;
14
- import android.hardware.camera2.CameraAccessException;
15
- import android.hardware.camera2.CameraCharacteristics;
16
- import android.hardware.camera2.CameraManager;
17
- import android.hardware.camera2.params.StreamConfigurationMap;
18
8
  import android.util.DisplayMetrics;
19
- import android.util.Size;
20
- import android.util.TypedValue;
21
- import android.view.Display;
22
- import android.view.MotionEvent;
23
- import android.view.View;
24
- import android.view.ViewGroup;
25
- import android.widget.FrameLayout;
26
- import androidx.annotation.NonNull;
27
9
  import com.getcapacitor.JSArray;
28
10
  import com.getcapacitor.JSObject;
29
- import com.getcapacitor.Logger;
30
11
  import com.getcapacitor.PermissionState;
31
12
  import com.getcapacitor.Plugin;
32
13
  import com.getcapacitor.PluginCall;
@@ -34,11 +15,22 @@ import com.getcapacitor.PluginMethod;
34
15
  import com.getcapacitor.annotation.CapacitorPlugin;
35
16
  import com.getcapacitor.annotation.Permission;
36
17
  import com.getcapacitor.annotation.PermissionCallback;
37
- import java.io.File;
38
- import java.util.ArrayList;
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;
39
21
  import java.util.List;
40
22
  import java.util.Objects;
41
- import org.json.JSONArray;
23
+ import android.util.Size;
24
+ import android.util.Log;
25
+ import com.ahm.capacitor.camera.preview.model.LensInfo;
26
+ import com.google.android.gms.location.FusedLocationProviderClient;
27
+ import com.google.android.gms.location.LocationServices;
28
+ import org.json.JSONObject;
29
+ import android.location.Location;
30
+ import android.view.ViewGroup;
31
+
32
+ import com.getcapacitor.Logger;
33
+
42
34
 
43
35
  @CapacitorPlugin(
44
36
  name = "CameraPreview",
@@ -51,33 +43,31 @@ import org.json.JSONArray;
51
43
  strings = { CAMERA },
52
44
  alias = CameraPreview.CAMERA_ONLY_PERMISSION_ALIAS
53
45
  ),
46
+ @Permission(
47
+ strings = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION },
48
+ alias = CameraPreview.CAMERA_WITH_LOCATION_PERMISSION_ALIAS
49
+ )
54
50
  }
55
51
  )
56
52
  public class CameraPreview
57
53
  extends Plugin
58
- implements CameraActivity.CameraPreviewListener {
54
+ implements CameraXView.CameraXViewListener {
59
55
 
60
56
  static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
61
57
  static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
62
-
63
- private static String VIDEO_FILE_PATH = "";
64
- private static final String VIDEO_FILE_EXTENSION = ".mp4";
58
+ static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS = "cameraWithLocation";
65
59
 
66
60
  private String captureCallbackId = "";
67
61
  private String snapshotCallbackId = "";
68
- private String recordCallbackId = "";
69
62
  private String cameraStartCallbackId = "";
70
-
71
- // keep track of previously specified orientation to support locking orientation:
72
- private int previousOrientationRequest =
73
- ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
74
-
75
- private CameraActivity fragment;
76
- private final int containerViewId = 20;
63
+ private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
64
+ private CameraXView cameraXView;
65
+ private FusedLocationProviderClient fusedLocationClient;
66
+ private Location lastLocation;
77
67
 
78
68
  @PluginMethod
79
69
  public void start(PluginCall call) {
80
- boolean disableAudio = call.getBoolean("disableAudio", false);
70
+ boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
81
71
  String permissionAlias = disableAudio
82
72
  ? CAMERA_ONLY_PERMISSION_ALIAS
83
73
  : CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
@@ -95,118 +85,83 @@ public class CameraPreview
95
85
 
96
86
  @PluginMethod
97
87
  public void flip(PluginCall call) {
98
- try {
99
- fragment.switchCamera();
100
- call.resolve();
101
- } catch (Exception e) {
102
- Logger.debug(getLogTag(), "Camera flip exception: " + e);
103
- call.reject("failed to flip camera");
88
+ if (cameraXView == null || !cameraXView.isRunning()) {
89
+ call.reject("Camera is not running");
90
+ return;
104
91
  }
92
+ cameraXView.flipCamera();
93
+ call.resolve();
105
94
  }
106
95
 
107
96
  @PluginMethod
108
- public void setOpacity(PluginCall call) {
109
- if (!this.hasCamera(call)) {
97
+ public void capture(final PluginCall call) {
98
+ if (cameraXView == null || !cameraXView.isRunning()) {
110
99
  call.reject("Camera is not running");
111
100
  return;
112
101
  }
113
102
 
114
- bridge.saveCall(call);
115
- Float opacity = Objects.requireNonNull(call.getFloat("opacity", 1F));
116
- fragment.setOpacity(opacity);
103
+ final boolean withExifLocation = call.getBoolean("withExifLocation", false);
104
+
105
+ if (withExifLocation) {
106
+ if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) != PermissionState.GRANTED) {
107
+ requestPermissionForAlias(CAMERA_WITH_LOCATION_PERMISSION_ALIAS, call, "captureWithLocationPermission");
108
+ } else {
109
+ getLocationAndCapture(call);
110
+ }
111
+ } else {
112
+ captureWithoutLocation(call);
113
+ }
117
114
  }
118
115
 
119
- @PluginMethod
120
- public void capture(PluginCall call) {
121
- if (!this.hasCamera(call)) {
122
- call.reject("Camera is not running");
123
- return;
116
+ @PermissionCallback
117
+ private void captureWithLocationPermission(PluginCall call) {
118
+ if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) == PermissionState.GRANTED) {
119
+ getLocationAndCapture(call);
120
+ } else {
121
+ Logger.warn("Location permission denied. Capturing photo without location data.");
122
+ captureWithoutLocation(call);
124
123
  }
124
+ }
125
+
126
+ private void getLocationAndCapture(PluginCall call) {
127
+ if (fusedLocationClient == null) {
128
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(getContext());
129
+ }
130
+ fusedLocationClient.getLastLocation().addOnSuccessListener(getActivity(), location -> {
131
+ lastLocation = location;
132
+ proceedWithCapture(call, lastLocation);
133
+ }).addOnFailureListener(e -> {
134
+ Logger.error("Failed to get location: " + e.getMessage());
135
+ proceedWithCapture(call, null);
136
+ });
137
+ }
138
+
139
+ private void captureWithoutLocation(PluginCall call) {
140
+ proceedWithCapture(call, null);
141
+ }
142
+
143
+ private void proceedWithCapture(PluginCall call, Location location) {
125
144
  bridge.saveCall(call);
126
145
  captureCallbackId = call.getCallbackId();
127
146
 
128
147
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
129
- // Image Dimensions - Optional
130
- Integer width = Objects.requireNonNull(call.getInt("width", 0));
131
- Integer height = Objects.requireNonNull(call.getInt("height", 0));
132
- fragment.takePicture(width, height, quality);
148
+ final boolean saveToGallery = call.getBoolean("saveToGallery", false);
149
+ Integer width = call.getInt("width");
150
+ Integer height = call.getInt("height");
151
+
152
+ cameraXView.capturePhoto(quality, saveToGallery, width, height, location);
133
153
  }
134
154
 
135
155
  @PluginMethod
136
156
  public void captureSample(PluginCall call) {
137
- if (!this.hasCamera(call)) {
157
+ if (cameraXView == null || !cameraXView.isRunning()) {
138
158
  call.reject("Camera is not running");
139
159
  return;
140
160
  }
141
161
  bridge.saveCall(call);
142
162
  snapshotCallbackId = call.getCallbackId();
143
-
144
163
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
145
- fragment.takeSnapshot(quality);
146
- }
147
-
148
- @PluginMethod
149
- public void getSupportedPictureSizes(final PluginCall call) {
150
- CameraManager cameraManager = (CameraManager) this.getContext()
151
- .getSystemService(Context.CAMERA_SERVICE);
152
-
153
- JSArray ret = new JSArray();
154
- try {
155
- String[] cameraIdList = cameraManager.getCameraIdList();
156
- for (String cameraId : cameraIdList) {
157
- CameraCharacteristics characteristics =
158
- cameraManager.getCameraCharacteristics(cameraId);
159
-
160
- // Determine the facing of the camera
161
- Integer lensFacing = characteristics.get(
162
- CameraCharacteristics.LENS_FACING
163
- );
164
- String facing = "Unknown";
165
- if (lensFacing != null) {
166
- switch (lensFacing) {
167
- case CameraCharacteristics.LENS_FACING_FRONT:
168
- facing = "Front";
169
- break;
170
- case CameraCharacteristics.LENS_FACING_BACK:
171
- facing = "Back";
172
- break;
173
- case CameraCharacteristics.LENS_FACING_EXTERNAL:
174
- facing = "External";
175
- break;
176
- }
177
- }
178
-
179
- StreamConfigurationMap map = characteristics.get(
180
- CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
181
- );
182
- if (map == null) {
183
- continue;
184
- }
185
-
186
- Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG);
187
- JSObject camera = new JSObject();
188
- camera.put("facing", facing);
189
- JSArray supportedPictureSizes = new JSArray();
190
- if (jpegSizes != null) {
191
- for (Size size : jpegSizes) {
192
- JSObject sizeJson = new JSObject();
193
- sizeJson.put("width", size.getWidth());
194
- sizeJson.put("height", size.getHeight());
195
- supportedPictureSizes.put(sizeJson);
196
- }
197
- camera.put("supportedPictureSizes", supportedPictureSizes);
198
- ret.put(camera);
199
- }
200
- }
201
- JSObject finalRet = new JSObject();
202
- finalRet.put("supportedPictureSizes", ret);
203
- call.resolve(finalRet);
204
- } catch (CameraAccessException ex) {
205
- Logger.error(getLogTag(), "Cannot call getSupportedPictureSizes", ex);
206
- call.reject(
207
- String.format("Cannot call getSupportedPictureSizes. Error: %s", ex)
208
- );
209
- }
164
+ cameraXView.captureSample(quality);
210
165
  }
211
166
 
212
167
  @PluginMethod
@@ -214,190 +169,204 @@ public class CameraPreview
214
169
  bridge
215
170
  .getActivity()
216
171
  .runOnUiThread(
217
- new Runnable() {
218
- @Override
219
- public void run() {
220
- FrameLayout containerView = getBridge()
221
- .getActivity()
222
- .findViewById(containerViewId);
223
-
224
- // allow orientation changes after closing camera:
225
- getBridge()
226
- .getActivity()
227
- .setRequestedOrientation(previousOrientationRequest);
228
-
229
- if (containerView != null) {
230
- ((ViewGroup) getBridge().getWebView().getParent()).removeView(
231
- containerView
232
- );
233
- getBridge().getWebView().setBackgroundColor(Color.WHITE);
234
- FragmentManager fragmentManager = getActivity()
235
- .getFragmentManager();
236
- FragmentTransaction fragmentTransaction =
237
- fragmentManager.beginTransaction();
238
- fragmentTransaction.remove(fragment);
239
- fragmentTransaction.commit();
240
- fragment = null;
241
-
242
- call.resolve();
243
- } else {
244
- call.reject("camera already stopped");
245
- }
172
+ () -> {
173
+ getBridge()
174
+ .getActivity()
175
+ .setRequestedOrientation(previousOrientationRequest);
176
+
177
+ if (cameraXView != null && cameraXView.isRunning()) {
178
+ cameraXView.stopSession();
179
+ cameraXView = null;
246
180
  }
181
+ call.resolve();
247
182
  }
248
183
  );
249
184
  }
250
185
 
251
186
  @PluginMethod
252
187
  public void getSupportedFlashModes(PluginCall call) {
253
- if (!this.hasCamera(call)) {
254
- call.reject("Camera is not running");
255
- return;
188
+ List<String> supportedFlashModes = cameraXView.getSupportedFlashModes();
189
+ JSArray jsonFlashModes = new JSArray();
190
+ for (String mode : supportedFlashModes) {
191
+ jsonFlashModes.put(mode);
256
192
  }
257
-
258
- Camera camera = fragment.getCamera();
259
- Camera.Parameters params = camera.getParameters();
260
- List<String> supportedFlashModes;
261
- supportedFlashModes = params.getSupportedFlashModes();
262
- JSONArray jsonFlashModes = new JSONArray();
263
-
264
- if (supportedFlashModes != null) {
265
- for (int i = 0; i < supportedFlashModes.size(); i++) {
266
- jsonFlashModes.put(supportedFlashModes.get(i));
267
- }
268
- }
269
-
270
193
  JSObject jsObject = new JSObject();
271
194
  jsObject.put("result", jsonFlashModes);
272
195
  call.resolve(jsObject);
273
196
  }
274
197
 
275
198
  @PluginMethod
276
- public void getHorizontalFov(PluginCall call) {
277
- if (!this.hasCamera(call)) {
278
- call.reject("Camera is not running");
199
+ public void setFlashMode(PluginCall call) {
200
+ String flashMode = call.getString("flashMode");
201
+ if (flashMode == null || flashMode.isEmpty()) {
202
+ call.reject("flashMode required parameter is missing");
279
203
  return;
280
204
  }
205
+ cameraXView.setFlashMode(flashMode);
206
+ call.resolve();
207
+ }
281
208
 
282
- Camera camera = fragment.getCamera();
283
- Camera.Parameters params = camera.getParameters();
284
-
285
- float horizontalViewAngle = params.getHorizontalViewAngle();
209
+ @PluginMethod
210
+ public void getAvailableDevices(PluginCall call) {
211
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
212
+ JSArray devicesArray = new JSArray();
213
+ for (CameraDevice device : devices) {
214
+ JSObject deviceJson = new JSObject();
215
+ deviceJson.put("deviceId", device.getDeviceId());
216
+ deviceJson.put("label", device.getLabel());
217
+ deviceJson.put("position", device.getPosition());
218
+ JSArray lensesArray = new JSArray();
219
+ for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
220
+ JSObject lensJson = new JSObject();
221
+ lensJson.put("focalLength", lens.getFocalLength());
222
+ lensJson.put("deviceType", lens.getDeviceType());
223
+ lensJson.put("baseZoomRatio", lens.getBaseZoomRatio());
224
+ lensJson.put("digitalZoom", lens.getDigitalZoom());
225
+ lensesArray.put(lensJson);
226
+ }
227
+ deviceJson.put("lenses", lensesArray);
228
+ deviceJson.put("minZoom", device.getMinZoom());
229
+ deviceJson.put("maxZoom", device.getMaxZoom());
230
+ devicesArray.put(deviceJson);
231
+ }
232
+ JSObject result = new JSObject();
233
+ result.put("devices", devicesArray);
234
+ call.resolve(result);
235
+ }
286
236
 
287
- JSObject jsObject = new JSObject();
288
- jsObject.put("result", horizontalViewAngle);
289
- call.resolve(jsObject);
237
+ @PluginMethod
238
+ public void getZoom(PluginCall call) {
239
+ ZoomFactors zoomFactors = cameraXView.getZoomFactors();
240
+ JSObject result = new JSObject();
241
+ result.put("min", zoomFactors.getMin());
242
+ result.put("max", zoomFactors.getMax());
243
+ result.put("current", zoomFactors.getCurrent());
244
+ call.resolve(result);
290
245
  }
291
246
 
292
247
  @PluginMethod
293
- public void setFlashMode(PluginCall call) {
294
- if (!this.hasCamera(call)) {
248
+ public void setZoom(PluginCall call) {
249
+ if (cameraXView == null || !cameraXView.isRunning()) {
295
250
  call.reject("Camera is not running");
296
251
  return;
297
252
  }
298
-
299
- String flashMode = call.getString("flashMode");
300
- if (flashMode == null || flashMode.isEmpty()) {
301
- call.reject("flashMode required parameter is missing");
253
+ Float level = call.getFloat("level");
254
+ if (level == null) {
255
+ call.reject("level parameter is required");
302
256
  return;
303
257
  }
258
+ try {
259
+ cameraXView.setZoom(level);
260
+ call.resolve();
261
+ } catch (Exception e) {
262
+ call.reject("Failed to set zoom: " + e.getMessage());
263
+ }
264
+ }
304
265
 
305
- Camera camera = fragment.getCamera();
306
- Camera.Parameters params = camera.getParameters();
307
-
308
- List<String> supportedFlashModes;
309
- supportedFlashModes = camera.getParameters().getSupportedFlashModes();
310
- if (
311
- supportedFlashModes != null && supportedFlashModes.contains(flashMode)
312
- ) {
313
- params.setFlashMode(flashMode);
314
- } else {
315
- call.reject("Flash mode not recognised: " + flashMode);
266
+ @PluginMethod
267
+ public void setDeviceId(PluginCall call) {
268
+ String deviceId = call.getString("deviceId");
269
+ if (deviceId == null || deviceId.isEmpty()) {
270
+ call.reject("deviceId parameter is required");
316
271
  return;
317
272
  }
273
+ if (cameraXView == null || !cameraXView.isRunning()) {
274
+ call.reject("Camera is not running");
275
+ return;
276
+ }
277
+ cameraXView.switchToDevice(deviceId);
278
+ call.resolve();
279
+ }
318
280
 
319
- fragment.setCameraParameters(params);
281
+ @PluginMethod
282
+ public void getSupportedPictureSizes(final PluginCall call) {
283
+ JSArray supportedPictureSizesResult = new JSArray();
284
+ List<Size> rearSizes = CameraXView.getSupportedPictureSizes("rear");
285
+ JSObject rear = new JSObject();
286
+ rear.put("facing", "rear");
287
+ JSArray rearSizesJs = new JSArray();
288
+ for(Size size : rearSizes) {
289
+ JSObject sizeJs = new JSObject();
290
+ sizeJs.put("width", size.getWidth());
291
+ sizeJs.put("height", size.getHeight());
292
+ rearSizesJs.put(sizeJs);
293
+ }
294
+ rear.put("supportedPictureSizes", rearSizesJs);
295
+ supportedPictureSizesResult.put(rear);
296
+
297
+ List<Size> frontSizes = CameraXView.getSupportedPictureSizes("front");
298
+ JSObject front = new JSObject();
299
+ front.put("facing", "front");
300
+ JSArray frontSizesJs = new JSArray();
301
+ for(Size size : frontSizes) {
302
+ JSObject sizeJs = new JSObject();
303
+ sizeJs.put("width", size.getWidth());
304
+ sizeJs.put("height", size.getHeight());
305
+ frontSizesJs.put(sizeJs);
306
+ }
307
+ front.put("supportedPictureSizes", frontSizesJs);
308
+ supportedPictureSizesResult.put(front);
320
309
 
321
- call.resolve();
310
+ JSObject ret = new JSObject();
311
+ ret.put("supportedPictureSizes", supportedPictureSizesResult);
312
+ call.resolve(ret);
322
313
  }
323
314
 
324
315
  @PluginMethod
325
- public void startRecordVideo(final PluginCall call) {
326
- if (!this.hasCamera(call)) {
316
+ public void setOpacity(PluginCall call) {
317
+ if (cameraXView == null || !cameraXView.isRunning()) {
327
318
  call.reject("Camera is not running");
328
319
  return;
329
320
  }
330
- final String filename = "videoTmp";
331
- VIDEO_FILE_PATH = getActivity().getCacheDir().toString() + "/";
332
-
333
- final String position = Objects.requireNonNull(
334
- call.getString("position", "front")
335
- );
336
- final Integer width = Objects.requireNonNull(call.getInt("width", 0));
337
- final Integer height = Objects.requireNonNull(call.getInt("height", 0));
338
- final Boolean withFlash = Objects.requireNonNull(
339
- call.getBoolean("withFlash", false)
340
- );
341
- final Integer maxDuration = Objects.requireNonNull(
342
- call.getInt("maxDuration", 0)
343
- );
344
- // final Integer quality = Objects.requireNonNull(call.getInt("quality", 0));
345
- bridge.saveCall(call);
346
- recordCallbackId = call.getCallbackId();
347
-
348
- bridge
349
- .getActivity()
350
- .runOnUiThread(
351
- new Runnable() {
352
- @Override
353
- public void run() {
354
- fragment.startRecord(
355
- getFilePath(),
356
- position,
357
- width,
358
- height,
359
- 70,
360
- withFlash,
361
- maxDuration
362
- );
363
- }
364
- }
365
- );
366
-
321
+ Float opacity = call.getFloat("opacity", 1.0f);
322
+ cameraXView.setOpacity(opacity);
367
323
  call.resolve();
368
324
  }
369
325
 
370
326
  @PluginMethod
371
- public void stopRecordVideo(PluginCall call) {
372
- if (!this.hasCamera(call)) {
327
+ public void getHorizontalFov(PluginCall call) {
328
+ // CameraX does not provide a simple way to get FoV.
329
+ // This would require Camera2 interop to access camera characteristics.
330
+ // Returning a default/estimated value.
331
+ JSObject ret = new JSObject();
332
+ ret.put("result", 60.0); // A common default FoV
333
+ call.resolve(ret);
334
+ }
335
+
336
+ @PluginMethod
337
+ public void getDeviceId(PluginCall call) {
338
+ if (cameraXView == null || !cameraXView.isRunning()) {
373
339
  call.reject("Camera is not running");
374
340
  return;
375
341
  }
342
+ JSObject ret = new JSObject();
343
+ ret.put("deviceId", cameraXView.getCurrentDeviceId());
344
+ call.resolve(ret);
345
+ }
376
346
 
377
- System.out.println("stopRecordVideo - Callbackid=" + call.getCallbackId());
378
-
379
- bridge.saveCall(call);
380
- recordCallbackId = call.getCallbackId();
381
-
382
- // bridge.getActivity().runOnUiThread(new Runnable() {
383
- // @Override
384
- // public void run() {
385
- // fragment.stopRecord();
386
- // }
387
- // });
347
+ @PluginMethod
348
+ public void getFlashMode(PluginCall call) {
349
+ if (cameraXView == null || !cameraXView.isRunning()) {
350
+ call.reject("Camera is not running");
351
+ return;
352
+ }
353
+ JSObject ret = new JSObject();
354
+ ret.put("flashMode", cameraXView.getFlashMode());
355
+ call.resolve(ret);
356
+ }
388
357
 
389
- fragment.stopRecord();
390
- // call.resolve();
358
+ @PluginMethod
359
+ public void isRunning(PluginCall call) {
360
+ boolean running = cameraXView != null && cameraXView.isRunning();
361
+ JSObject jsObject = new JSObject();
362
+ jsObject.put("isRunning", running);
363
+ call.resolve(jsObject);
391
364
  }
392
365
 
393
366
  @PermissionCallback
394
367
  private void handleCameraPermissionResult(PluginCall call) {
395
- boolean disableAudio = call.getBoolean("disableAudio", false);
396
- String permissionAlias = disableAudio
397
- ? CAMERA_ONLY_PERMISSION_ALIAS
398
- : CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
399
-
400
- if (PermissionState.GRANTED.equals(getPermissionState(permissionAlias))) {
368
+ if (PermissionState.GRANTED.equals(getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)) ||
369
+ PermissionState.GRANTED.equals(getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS))) {
401
370
  startCamera(call);
402
371
  } else {
403
372
  call.reject("Permission failed");
@@ -405,327 +374,289 @@ public class CameraPreview
405
374
  }
406
375
 
407
376
  private void startCamera(final PluginCall call) {
408
- String position = call.getString("position");
409
-
410
- if (position == null || position.isEmpty() || "rear".equals(position)) {
411
- position = "back";
412
- } else {
413
- position = "front";
414
- }
415
-
416
- @NonNull
417
- final Integer x = Objects.requireNonNull(call.getInt("x", 0));
418
- @NonNull
419
- final Integer y = Objects.requireNonNull(call.getInt("y", 0));
420
- @NonNull
421
- final Integer width = Objects.requireNonNull(call.getInt("width", 0));
422
- @NonNull
423
- final Integer height = Objects.requireNonNull(call.getInt("height", 0));
424
- @NonNull
425
- final Integer paddingBottom = Objects.requireNonNull(
426
- call.getInt("paddingBottom", 0)
427
- );
428
- final Boolean toBack = Objects.requireNonNull(
429
- call.getBoolean("toBack", false)
430
- );
431
- final Boolean storeToFile = Objects.requireNonNull(
432
- call.getBoolean("storeToFile", false)
433
- );
434
- final Boolean enableOpacity = Objects.requireNonNull(
435
- call.getBoolean("enableOpacity", false)
436
- );
437
- final Boolean enableZoom = Objects.requireNonNull(
438
- call.getBoolean("enableZoom", false)
439
- );
440
- final Boolean disableExifHeaderStripping = Objects.requireNonNull(
441
- call.getBoolean("disableExifHeaderStripping", false)
442
- );
443
- final Boolean lockOrientation = Objects.requireNonNull(
444
- call.getBoolean("lockAndroidOrientation", false)
445
- );
446
- previousOrientationRequest = getBridge()
447
- .getActivity()
448
- .getRequestedOrientation();
449
-
450
- fragment = new CameraActivity();
451
- fragment.setEventListener(this);
452
- fragment.defaultCamera = position;
453
- fragment.tapToTakePicture = false;
454
- fragment.dragEnabled = false;
455
- fragment.tapToFocus = true;
456
- fragment.disableExifHeaderStripping = disableExifHeaderStripping;
457
- fragment.storeToFile = storeToFile;
458
- fragment.toBack = toBack;
459
- fragment.enableOpacity = enableOpacity;
460
- fragment.enableZoom = enableZoom;
461
- fragment.disableAudio = call.getBoolean("disableAudio", false);
462
-
463
- bridge
464
- .getActivity()
465
- .runOnUiThread(
466
- new Runnable() {
467
- @Override
468
- public void run() {
469
- DisplayMetrics metrics = getBridge()
470
- .getActivity()
471
- .getResources()
472
- .getDisplayMetrics();
473
- // lock orientation if specified in options:
474
- if (lockOrientation) {
475
- getBridge()
476
- .getActivity()
477
- .setRequestedOrientation(
478
- ActivityInfo.SCREEN_ORIENTATION_LOCKED
479
- );
480
- }
481
-
482
- // offset
483
- int computedX = (int) TypedValue.applyDimension(
484
- TypedValue.COMPLEX_UNIT_DIP,
485
- x,
486
- metrics
487
- );
488
- int computedY = (int) TypedValue.applyDimension(
489
- TypedValue.COMPLEX_UNIT_DIP,
490
- y,
491
- metrics
492
- );
493
-
494
- // size
495
- int computedWidth;
496
- int computedHeight;
497
- int computedPaddingBottom;
498
-
499
- if (paddingBottom != 0) {
500
- computedPaddingBottom = (int) TypedValue.applyDimension(
501
- TypedValue.COMPLEX_UNIT_DIP,
502
- paddingBottom,
503
- metrics
504
- );
505
- } else {
506
- computedPaddingBottom = 0;
507
- }
377
+ String positionParam = call.getString("position");
378
+ String originalDeviceId = call.getString("deviceId");
379
+ String deviceId = originalDeviceId; // Use a mutable variable
380
+
381
+ final String position = (positionParam == null || positionParam.isEmpty() || "rear".equals(positionParam) || "back".equals(positionParam)) ? "back" : "front";
382
+ final int x = call.getInt("x", 0);
383
+ final int y = call.getInt("y", 0);
384
+ final int width = call.getInt("width", 0);
385
+ final int height = call.getInt("height", 0);
386
+ final int paddingBottom = call.getInt("paddingBottom", 0);
387
+ final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
388
+ final boolean storeToFile = Boolean.TRUE.equals(call.getBoolean("storeToFile", false));
389
+ final boolean enableOpacity = Boolean.TRUE.equals(call.getBoolean("enableOpacity", false));
390
+ final boolean enableZoom = Boolean.TRUE.equals(call.getBoolean("enableZoom", false));
391
+ final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
392
+ final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
393
+ final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
394
+ final String aspectRatio = call.getString("aspectRatio", "4:3");
395
+ final String gridMode = call.getString("gridMode", "none");
396
+
397
+ // Check for conflict between aspectRatio and size
398
+ if (call.getData().has("aspectRatio") && (call.getData().has("width") || call.getData().has("height"))) {
399
+ call.reject("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.");
400
+ return;
401
+ }
508
402
 
509
- if (width != 0) {
510
- computedWidth = (int) TypedValue.applyDimension(
511
- TypedValue.COMPLEX_UNIT_DIP,
512
- width,
513
- metrics
514
- );
515
- } else {
516
- Display defaultDisplay = getBridge()
517
- .getActivity()
518
- .getWindowManager()
519
- .getDefaultDisplay();
520
- final Point size = new Point();
521
- defaultDisplay.getSize(size);
522
-
523
- computedWidth = (int) TypedValue.applyDimension(
524
- TypedValue.COMPLEX_UNIT_PX,
525
- size.x,
526
- metrics
527
- );
403
+ float targetZoom = 1.0f;
404
+ // Check if the selected device is a physical ultra-wide
405
+ if (originalDeviceId != null) {
406
+ List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(getContext());
407
+ for (CameraDevice device : devices) {
408
+ if (originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()) {
409
+ for (LensInfo lens : device.getLenses()) {
410
+ if ("ultraWide".equals(lens.getDeviceType())) {
411
+ Log.d("CameraPreview", "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera.");
412
+ targetZoom = 0.5f;
413
+ // Force the use of the logical camera by clearing the specific deviceId
414
+ deviceId = null;
415
+ break;
416
+ }
417
+ }
528
418
  }
419
+ if (deviceId == null) break; // Exit outer loop once we've made our decision
420
+ }
421
+ }
529
422
 
530
- if (height != 0) {
531
- computedHeight =
532
- (int) TypedValue.applyDimension(
533
- TypedValue.COMPLEX_UNIT_DIP,
534
- height,
535
- metrics
536
- ) -
537
- computedPaddingBottom;
538
- } else {
539
- Display defaultDisplay = getBridge()
540
- .getActivity()
541
- .getWindowManager()
542
- .getDefaultDisplay();
543
- final Point size = new Point();
544
- defaultDisplay.getSize(size);
545
-
546
- computedHeight =
547
- (int) TypedValue.applyDimension(
548
- TypedValue.COMPLEX_UNIT_PX,
549
- size.y,
550
- metrics
551
- ) -
552
- computedPaddingBottom;
553
- }
423
+ previousOrientationRequest = getBridge().getActivity().getRequestedOrientation();
424
+ cameraXView = new CameraXView(getContext(), getBridge().getWebView());
425
+ cameraXView.setListener(this);
554
426
 
555
- fragment.setRect(
556
- computedX,
557
- computedY,
558
- computedWidth,
559
- computedHeight
560
- );
561
-
562
- FrameLayout containerView = getBridge()
563
- .getActivity()
564
- .findViewById(containerViewId);
565
- if (containerView == null) {
566
- containerView = new FrameLayout(
567
- getActivity().getApplicationContext()
568
- );
569
- containerView.setId(containerViewId);
570
-
571
- getBridge().getWebView().setBackgroundColor(Color.TRANSPARENT);
572
- ((ViewGroup) getBridge().getWebView().getParent()).addView(
573
- containerView
574
- );
575
- if (toBack) {
576
- getBridge()
577
- .getWebView()
578
- .getParent()
579
- .bringChildToFront(getBridge().getWebView());
580
- setupBroadcast();
581
- }
582
-
583
- FragmentManager fragmentManager = getBridge()
584
- .getActivity()
585
- .getFragmentManager();
586
- FragmentTransaction fragmentTransaction =
587
- fragmentManager.beginTransaction();
588
- fragmentTransaction.add(containerView.getId(), fragment);
589
- fragmentTransaction.commit();
590
-
591
- // NOTE: we don't return invoke call.resolve here because it must be invoked in onCameraStarted
592
- // otherwise the plugin start method might resolve/return before the camera is actually set in CameraActivity
593
- // onResume method (see this line mCamera = Camera.open(defaultCameraId);) and the next subsequent plugin
594
- // method invocations (for example, getSupportedFlashModes) might fails with "Camera is not running" error
595
- // because camera is not available yet and hasCamera method will return false
596
- // Please also see https://developer.android.com/reference/android/hardware/Camera.html#open%28int%29
597
- bridge.saveCall(call);
598
- cameraStartCallbackId = call.getCallbackId();
599
- } else {
600
- call.reject("camera already started");
601
- }
602
- }
427
+ String finalDeviceId = deviceId;
428
+ float finalTargetZoom = targetZoom;
429
+ getBridge().getActivity().runOnUiThread(() -> {
430
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
431
+ if (lockOrientation) {
432
+ getBridge().getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
603
433
  }
604
- );
605
- }
606
-
607
- @Override
608
- protected void handleOnResume() {
609
- super.handleOnResume();
434
+
435
+ // Debug: Let's check all the positioning information
436
+ ViewGroup webViewParent = (ViewGroup) getBridge().getWebView().getParent();
437
+
438
+ // Get webview position in different coordinate systems
439
+ int[] webViewLocationInWindow = new int[2];
440
+ int[] webViewLocationOnScreen = new int[2];
441
+ getBridge().getWebView().getLocationInWindow(webViewLocationInWindow);
442
+ getBridge().getWebView().getLocationOnScreen(webViewLocationOnScreen);
443
+
444
+ int webViewLeft = getBridge().getWebView().getLeft();
445
+ int webViewTop = getBridge().getWebView().getTop();
446
+
447
+ // Check parent position too
448
+ int[] parentLocationInWindow = new int[2];
449
+ int[] parentLocationOnScreen = new int[2];
450
+ webViewParent.getLocationInWindow(parentLocationInWindow);
451
+ webViewParent.getLocationOnScreen(parentLocationOnScreen);
452
+
453
+ // Calculate pixel ratio
454
+ float pixelRatio = metrics.density;
455
+
456
+ // Try using just the pixel ratio without any webview offset for now
457
+ int computedX = (int) (x * pixelRatio);
458
+ int computedY = (int) (y * pixelRatio);
459
+
460
+ Log.d("CameraPreview", "=== COORDINATE DEBUG ===");
461
+ Log.d("CameraPreview", "WebView getLeft/getTop: (" + webViewLeft + ", " + webViewTop + ")");
462
+ Log.d("CameraPreview", "WebView locationInWindow: (" + webViewLocationInWindow[0] + ", " + webViewLocationInWindow[1] + ")");
463
+ Log.d("CameraPreview", "WebView locationOnScreen: (" + webViewLocationOnScreen[0] + ", " + webViewLocationOnScreen[1] + ")");
464
+ Log.d("CameraPreview", "Parent locationInWindow: (" + parentLocationInWindow[0] + ", " + parentLocationInWindow[1] + ")");
465
+ Log.d("CameraPreview", "Parent locationOnScreen: (" + parentLocationOnScreen[0] + ", " + parentLocationOnScreen[1] + ")");
466
+ Log.d("CameraPreview", "Parent class: " + webViewParent.getClass().getSimpleName());
467
+ Log.d("CameraPreview", "Requested position (logical): (" + x + ", " + y + ")");
468
+ Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
469
+ Log.d("CameraPreview", "Final computed position (no offset): (" + computedX + ", " + computedY + ")");
470
+ Log.d("CameraPreview", "========================");
471
+ int computedWidth = width != 0 ? (int) (width * pixelRatio) : getBridge().getWebView().getWidth();
472
+ int computedHeight = height != 0 ? (int) (height * pixelRatio) : getBridge().getWebView().getHeight();
473
+ computedHeight -= (int) (paddingBottom * pixelRatio);
474
+
475
+ CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f, aspectRatio, gridMode);
476
+ config.setTargetZoom(finalTargetZoom);
477
+
478
+ bridge.saveCall(call);
479
+ cameraStartCallbackId = call.getCallbackId();
480
+ cameraXView.startSession(config);
481
+ }
482
+ );
610
483
  }
611
484
 
612
485
  @Override
613
- public void onPictureTaken(String originalPicture) {
614
- JSObject jsObject = new JSObject();
615
- jsObject.put("value", originalPicture);
616
- bridge.getSavedCall(captureCallbackId).resolve(jsObject);
486
+ public void onPictureTaken(String base64, JSONObject exif) {
487
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
488
+ if (pluginCall == null) {
489
+ Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
490
+ return;
491
+ }
492
+ JSObject result = new JSObject();
493
+ result.put("value", base64);
494
+ result.put("exif", exif);
495
+ pluginCall.resolve(result);
496
+ bridge.releaseCall(pluginCall);
617
497
  }
618
498
 
619
499
  @Override
620
500
  public void onPictureTakenError(String message) {
621
- bridge.getSavedCall(captureCallbackId).reject(message);
622
- }
623
-
624
- @Override
625
- public void onSnapshotTaken(String originalPicture) {
626
- JSObject jsObject = new JSObject();
627
- jsObject.put("value", originalPicture);
628
- bridge.getSavedCall(snapshotCallbackId).resolve(jsObject);
501
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
502
+ if (pluginCall == null) {
503
+ Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
504
+ return;
505
+ }
506
+ pluginCall.reject(message);
507
+ bridge.releaseCall(pluginCall);
629
508
  }
630
509
 
631
510
  @Override
632
- public void onSnapshotTakenError(String message) {
633
- bridge.getSavedCall(snapshotCallbackId).reject(message);
511
+ public void onCameraStarted(int width, int height, int x, int y) {
512
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
513
+ if (call != null) {
514
+ // Convert pixel values back to logical units
515
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
516
+ float pixelRatio = metrics.density;
517
+
518
+ JSObject result = new JSObject();
519
+ result.put("width", width / pixelRatio);
520
+ result.put("height", height / pixelRatio);
521
+ result.put("x", x / pixelRatio);
522
+ result.put("y", y / pixelRatio);
523
+ call.resolve(result);
524
+ bridge.releaseCall(call);
525
+ cameraStartCallbackId = null; // Prevent re-use
526
+ }
634
527
  }
635
528
 
636
529
  @Override
637
- public void onFocusSet(int pointX, int pointY) {}
638
-
639
- @Override
640
- public void onFocusSetError(String message) {}
641
-
642
- @Override
643
- public void onBackButton() {}
644
-
645
- @Override
646
- public void onCameraStarted() {
647
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
648
- pluginCall.resolve();
649
- bridge.releaseCall(pluginCall);
530
+ public void onSampleTaken(String result) {
531
+ // Handle sample taken if needed
532
+ Log.i("CameraPreview", "Sample taken: " + result);
650
533
  }
651
534
 
652
535
  @Override
653
- public void onStartRecordVideo() {}
654
-
655
- @Override
656
- public void onStartRecordVideoError(String message) {
657
- bridge.getSavedCall(recordCallbackId).reject(message);
536
+ public void onSampleTakenError(String message) {
537
+ // Handle sample taken error if needed
538
+ Log.e("CameraPreview", "Sample taken error: " + message);
658
539
  }
659
540
 
660
541
  @Override
661
- public void onStopRecordVideo(String file) {
662
- PluginCall pluginCall = bridge.getSavedCall(recordCallbackId);
663
- JSObject jsObject = new JSObject();
664
- jsObject.put("videoFilePath", file);
665
- pluginCall.resolve(jsObject);
542
+ public void onCameraStartError(String message) {
543
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
544
+ if (call != null) {
545
+ call.reject(message);
546
+ bridge.releaseCall(call);
547
+ cameraStartCallbackId = null;
548
+ }
666
549
  }
667
550
 
668
- @Override
669
- public void onStopRecordVideoError(String error) {
670
- bridge.getSavedCall(recordCallbackId).reject(error);
551
+ @PluginMethod
552
+ public void setAspectRatio(PluginCall call) {
553
+ if (cameraXView == null || !cameraXView.isRunning()) {
554
+ call.reject("Camera is not running");
555
+ return;
556
+ }
557
+ String aspectRatio = call.getString("aspectRatio", "4:3");
558
+ Float x = call.getFloat("x");
559
+ Float y = call.getFloat("y");
560
+
561
+ getActivity().runOnUiThread(() -> {
562
+ cameraXView.setAspectRatio(aspectRatio, x, y, () -> {
563
+ // Return the actual preview bounds after layout and camera operations are complete
564
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
565
+ JSObject ret = new JSObject();
566
+ ret.put("x", bounds[0]);
567
+ ret.put("y", bounds[1]);
568
+ ret.put("width", bounds[2]);
569
+ ret.put("height", bounds[3]);
570
+ call.resolve(ret);
571
+ });
572
+ });
671
573
  }
672
574
 
673
- private boolean hasView(PluginCall call) {
674
- if (fragment == null) {
675
- return false;
575
+ @PluginMethod
576
+ public void getAspectRatio(PluginCall call) {
577
+ if (cameraXView == null || !cameraXView.isRunning()) {
578
+ call.reject("Camera is not running");
579
+ return;
676
580
  }
677
-
678
- return true;
581
+ String aspectRatio = cameraXView.getAspectRatio();
582
+ JSObject ret = new JSObject();
583
+ ret.put("aspectRatio", aspectRatio);
584
+ call.resolve(ret);
679
585
  }
680
586
 
681
- private boolean hasCamera(PluginCall call) {
682
- if (!this.hasView(call)) {
683
- return false;
587
+ @PluginMethod
588
+ public void setGridMode(PluginCall call) {
589
+ if (cameraXView == null || !cameraXView.isRunning()) {
590
+ call.reject("Camera is not running");
591
+ return;
684
592
  }
593
+ String gridMode = call.getString("gridMode", "none");
594
+ getActivity().runOnUiThread(() -> {
595
+ cameraXView.setGridMode(gridMode);
596
+ call.resolve();
597
+ });
598
+ }
685
599
 
686
- if (fragment.getCamera() == null) {
687
- return false;
600
+ @PluginMethod
601
+ public void getGridMode(PluginCall call) {
602
+ if (cameraXView == null || !cameraXView.isRunning()) {
603
+ call.reject("Camera is not running");
604
+ return;
688
605
  }
689
-
690
- return true;
606
+ JSObject ret = new JSObject();
607
+ ret.put("gridMode", cameraXView.getGridMode());
608
+ call.resolve(ret);
691
609
  }
692
610
 
693
- private String getFilePath() {
694
- String fileName = "videoTmp";
695
-
696
- int i = 1;
697
-
698
- while (
699
- new File(VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION).exists()
700
- ) {
701
- // Add number suffix if file exists
702
- fileName = "videoTmp" + '_' + i;
703
- i++;
611
+ @PluginMethod
612
+ public void getPreviewSize(PluginCall call) {
613
+ if (cameraXView == null || !cameraXView.isRunning()) {
614
+ call.reject("Camera is not running");
615
+ return;
704
616
  }
705
-
706
- return VIDEO_FILE_PATH + fileName + VIDEO_FILE_EXTENSION;
617
+
618
+ // Convert pixel values back to logical units
619
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
620
+ float pixelRatio = metrics.density;
621
+
622
+ JSObject ret = new JSObject();
623
+ ret.put("x", cameraXView.getPreviewX() / pixelRatio);
624
+ ret.put("y", cameraXView.getPreviewY() / pixelRatio);
625
+ ret.put("width", cameraXView.getPreviewWidth() / pixelRatio);
626
+ ret.put("height", cameraXView.getPreviewHeight() / pixelRatio);
627
+ call.resolve(ret);
707
628
  }
708
-
709
- private void setupBroadcast() {
710
- /** When touch event is triggered, relay it to camera view if needed so it can support pinch zoom */
711
-
712
- getBridge().getWebView().setClickable(true);
713
- getBridge()
714
- .getWebView()
715
- .setOnTouchListener(
716
- new View.OnTouchListener() {
717
- @Override
718
- public boolean onTouch(View v, MotionEvent event) {
719
- if (
720
- (null != fragment) &&
721
- (fragment.toBack == true) &&
722
- null != fragment.frameContainerLayout
723
- ) {
724
- fragment.frameContainerLayout.dispatchTouchEvent(event);
725
- }
726
- return false;
727
- }
728
- }
729
- );
629
+ @PluginMethod
630
+ public void setPreviewSize(PluginCall call) {
631
+ if (cameraXView == null || !cameraXView.isRunning()) {
632
+ call.reject("Camera is not running");
633
+ return;
634
+ }
635
+
636
+ // Get values from call - null values will become 0
637
+ Integer xParam = call.getInt("x");
638
+ Integer yParam = call.getInt("y");
639
+ Integer widthParam = call.getInt("width");
640
+ Integer heightParam = call.getInt("height");
641
+
642
+ // Apply pixel ratio conversion to non-null values
643
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
644
+ float pixelRatio = metrics.density;
645
+
646
+ int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
647
+ int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
648
+ int width = (widthParam != null && widthParam > 0) ? (int) (widthParam * pixelRatio) : 0;
649
+ int height = (heightParam != null && heightParam > 0) ? (int) (heightParam * pixelRatio) : 0;
650
+
651
+ cameraXView.setPreviewSize(x, y, width, height, () -> {
652
+ // Return the actual preview bounds after layout operations are complete
653
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
654
+ JSObject ret = new JSObject();
655
+ ret.put("x", bounds[0]);
656
+ ret.put("y", bounds[1]);
657
+ ret.put("width", bounds[2]);
658
+ ret.put("height", bounds[3]);
659
+ call.resolve(ret);
660
+ });
730
661
  }
731
662
  }