@capgo/camera-preview 7.4.0-beta.8 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -51
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +1249 -143
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +3400 -1382
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +95 -58
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +55 -46
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +61 -52
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +160 -72
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +29 -23
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +24 -23
- package/dist/docs.json +441 -40
- package/dist/esm/definitions.d.ts +167 -25
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +24 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +23 -3
- package/dist/esm/web.js +463 -65
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +485 -64
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +485 -64
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/{CapgoCameraPreview → CapgoCameraPreviewPlugin}/CameraController.swift +731 -315
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1902 -0
- package/package.json +11 -3
- package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
- package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
- package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.14.2/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
- package/android/.gradle/8.14.2/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
- package/android/.gradle/file-system.probe +0 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/ios/Sources/CapgoCameraPreview/Plugin.swift +0 -1211
- /package/ios/Sources/{CapgoCameraPreview → CapgoCameraPreviewPlugin}/GridOverlayView.swift +0 -0
|
@@ -5,9 +5,26 @@ import static android.Manifest.permission.RECORD_AUDIO;
|
|
|
5
5
|
|
|
6
6
|
import android.Manifest;
|
|
7
7
|
import android.content.pm.ActivityInfo;
|
|
8
|
+
import android.content.res.Configuration;
|
|
9
|
+
import android.graphics.Color;
|
|
10
|
+
import android.location.Location;
|
|
8
11
|
import android.util.DisplayMetrics;
|
|
12
|
+
import android.util.Log;
|
|
13
|
+
import android.util.Size;
|
|
14
|
+
import android.view.OrientationEventListener;
|
|
15
|
+
import android.view.View;
|
|
16
|
+
import android.view.ViewGroup;
|
|
17
|
+
import android.webkit.WebView;
|
|
18
|
+
import androidx.core.graphics.Insets;
|
|
19
|
+
import androidx.core.view.ViewCompat;
|
|
20
|
+
import androidx.core.view.WindowInsetsCompat;
|
|
21
|
+
import com.ahm.capacitor.camera.preview.model.CameraDevice;
|
|
22
|
+
import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
|
|
23
|
+
import com.ahm.capacitor.camera.preview.model.LensInfo;
|
|
24
|
+
import com.ahm.capacitor.camera.preview.model.ZoomFactors;
|
|
9
25
|
import com.getcapacitor.JSArray;
|
|
10
26
|
import com.getcapacitor.JSObject;
|
|
27
|
+
import com.getcapacitor.Logger;
|
|
11
28
|
import com.getcapacitor.PermissionState;
|
|
12
29
|
import com.getcapacitor.Plugin;
|
|
13
30
|
import com.getcapacitor.PluginCall;
|
|
@@ -15,22 +32,11 @@ import com.getcapacitor.PluginMethod;
|
|
|
15
32
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
16
33
|
import com.getcapacitor.annotation.Permission;
|
|
17
34
|
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;
|
|
21
|
-
import java.util.List;
|
|
22
|
-
import java.util.Objects;
|
|
23
|
-
import android.util.Size;
|
|
24
|
-
import android.util.Log;
|
|
25
|
-
import com.ahm.capacitor.camera.preview.model.LensInfo;
|
|
26
35
|
import com.google.android.gms.location.FusedLocationProviderClient;
|
|
27
36
|
import com.google.android.gms.location.LocationServices;
|
|
37
|
+
import java.util.List;
|
|
38
|
+
import java.util.Objects;
|
|
28
39
|
import org.json.JSONObject;
|
|
29
|
-
import android.location.Location;
|
|
30
|
-
import android.view.ViewGroup;
|
|
31
|
-
|
|
32
|
-
import com.getcapacitor.Logger;
|
|
33
|
-
|
|
34
40
|
|
|
35
41
|
@CapacitorPlugin(
|
|
36
42
|
name = "CameraPreview",
|
|
@@ -44,30 +50,62 @@ import com.getcapacitor.Logger;
|
|
|
44
50
|
alias = CameraPreview.CAMERA_ONLY_PERMISSION_ALIAS
|
|
45
51
|
),
|
|
46
52
|
@Permission(
|
|
47
|
-
strings = {
|
|
53
|
+
strings = {
|
|
54
|
+
Manifest.permission.ACCESS_COARSE_LOCATION,
|
|
55
|
+
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
56
|
+
},
|
|
48
57
|
alias = CameraPreview.CAMERA_WITH_LOCATION_PERMISSION_ALIAS
|
|
49
|
-
)
|
|
58
|
+
),
|
|
50
59
|
}
|
|
51
60
|
)
|
|
52
61
|
public class CameraPreview
|
|
53
62
|
extends Plugin
|
|
54
63
|
implements CameraXView.CameraXViewListener {
|
|
55
64
|
|
|
65
|
+
private static final String TAG = "CameraPreview CameraXView";
|
|
66
|
+
|
|
56
67
|
static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
|
|
57
68
|
static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
|
|
58
|
-
static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS =
|
|
69
|
+
static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS =
|
|
70
|
+
"cameraWithLocation";
|
|
59
71
|
|
|
60
72
|
private String captureCallbackId = "";
|
|
61
73
|
private String snapshotCallbackId = "";
|
|
62
74
|
private String cameraStartCallbackId = "";
|
|
63
|
-
private int previousOrientationRequest =
|
|
75
|
+
private int previousOrientationRequest =
|
|
76
|
+
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
|
64
77
|
private CameraXView cameraXView;
|
|
78
|
+
private View rotationOverlay;
|
|
65
79
|
private FusedLocationProviderClient fusedLocationClient;
|
|
66
80
|
private Location lastLocation;
|
|
81
|
+
private OrientationEventListener orientationListener;
|
|
82
|
+
private int lastOrientation = Configuration.ORIENTATION_UNDEFINED;
|
|
83
|
+
|
|
84
|
+
@PluginMethod
|
|
85
|
+
public void getOrientation(PluginCall call) {
|
|
86
|
+
int orientation = getContext()
|
|
87
|
+
.getResources()
|
|
88
|
+
.getConfiguration()
|
|
89
|
+
.orientation;
|
|
90
|
+
String o;
|
|
91
|
+
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
92
|
+
// We don't distinguish upside-down reliably on Android, report generic portrait
|
|
93
|
+
o = "portrait";
|
|
94
|
+
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
95
|
+
o = "landscape";
|
|
96
|
+
} else {
|
|
97
|
+
o = "unknown";
|
|
98
|
+
}
|
|
99
|
+
JSObject ret = new JSObject();
|
|
100
|
+
ret.put("orientation", o);
|
|
101
|
+
call.resolve(ret);
|
|
102
|
+
}
|
|
67
103
|
|
|
68
104
|
@PluginMethod
|
|
69
105
|
public void start(PluginCall call) {
|
|
70
|
-
boolean disableAudio = Boolean.TRUE.equals(
|
|
106
|
+
boolean disableAudio = Boolean.TRUE.equals(
|
|
107
|
+
call.getBoolean("disableAudio", true)
|
|
108
|
+
);
|
|
71
109
|
String permissionAlias = disableAudio
|
|
72
110
|
? CAMERA_ONLY_PERMISSION_ALIAS
|
|
73
111
|
: CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
|
|
@@ -103,8 +141,15 @@ public class CameraPreview
|
|
|
103
141
|
final boolean withExifLocation = call.getBoolean("withExifLocation", false);
|
|
104
142
|
|
|
105
143
|
if (withExifLocation) {
|
|
106
|
-
if (
|
|
107
|
-
|
|
144
|
+
if (
|
|
145
|
+
getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) !=
|
|
146
|
+
PermissionState.GRANTED
|
|
147
|
+
) {
|
|
148
|
+
requestPermissionForAlias(
|
|
149
|
+
CAMERA_WITH_LOCATION_PERMISSION_ALIAS,
|
|
150
|
+
call,
|
|
151
|
+
"captureWithLocationPermission"
|
|
152
|
+
);
|
|
108
153
|
} else {
|
|
109
154
|
getLocationAndCapture(call);
|
|
110
155
|
}
|
|
@@ -115,22 +160,32 @@ public class CameraPreview
|
|
|
115
160
|
|
|
116
161
|
@PermissionCallback
|
|
117
162
|
private void captureWithLocationPermission(PluginCall call) {
|
|
118
|
-
if (
|
|
163
|
+
if (
|
|
164
|
+
getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) ==
|
|
165
|
+
PermissionState.GRANTED
|
|
166
|
+
) {
|
|
119
167
|
getLocationAndCapture(call);
|
|
120
168
|
} else {
|
|
121
|
-
Logger.warn(
|
|
169
|
+
Logger.warn(
|
|
170
|
+
"Location permission denied. Capturing photo without location data."
|
|
171
|
+
);
|
|
122
172
|
captureWithoutLocation(call);
|
|
123
173
|
}
|
|
124
174
|
}
|
|
125
175
|
|
|
126
176
|
private void getLocationAndCapture(PluginCall call) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
177
|
+
if (fusedLocationClient == null) {
|
|
178
|
+
fusedLocationClient = LocationServices.getFusedLocationProviderClient(
|
|
179
|
+
getContext()
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
fusedLocationClient
|
|
183
|
+
.getLastLocation()
|
|
184
|
+
.addOnSuccessListener(getActivity(), location -> {
|
|
131
185
|
lastLocation = location;
|
|
132
186
|
proceedWithCapture(call, lastLocation);
|
|
133
|
-
})
|
|
187
|
+
})
|
|
188
|
+
.addOnFailureListener(e -> {
|
|
134
189
|
Logger.error("Failed to get location: " + e.getMessage());
|
|
135
190
|
proceedWithCapture(call, null);
|
|
136
191
|
});
|
|
@@ -148,8 +203,16 @@ public class CameraPreview
|
|
|
148
203
|
final boolean saveToGallery = call.getBoolean("saveToGallery", false);
|
|
149
204
|
Integer width = call.getInt("width");
|
|
150
205
|
Integer height = call.getInt("height");
|
|
206
|
+
String aspectRatio = call.getString("aspectRatio");
|
|
151
207
|
|
|
152
|
-
cameraXView.capturePhoto(
|
|
208
|
+
cameraXView.capturePhoto(
|
|
209
|
+
quality,
|
|
210
|
+
saveToGallery,
|
|
211
|
+
width,
|
|
212
|
+
height,
|
|
213
|
+
aspectRatio,
|
|
214
|
+
location
|
|
215
|
+
);
|
|
153
216
|
}
|
|
154
217
|
|
|
155
218
|
@PluginMethod
|
|
@@ -168,23 +231,38 @@ public class CameraPreview
|
|
|
168
231
|
public void stop(final PluginCall call) {
|
|
169
232
|
bridge
|
|
170
233
|
.getActivity()
|
|
171
|
-
.runOnUiThread(
|
|
172
|
-
()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.setRequestedOrientation(previousOrientationRequest);
|
|
234
|
+
.runOnUiThread(() -> {
|
|
235
|
+
getBridge()
|
|
236
|
+
.getActivity()
|
|
237
|
+
.setRequestedOrientation(previousOrientationRequest);
|
|
176
238
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
239
|
+
// Disable and clear orientation listener
|
|
240
|
+
if (orientationListener != null) {
|
|
241
|
+
orientationListener.disable();
|
|
242
|
+
orientationListener = null;
|
|
243
|
+
lastOrientation = Configuration.ORIENTATION_UNDEFINED;
|
|
182
244
|
}
|
|
183
|
-
|
|
245
|
+
|
|
246
|
+
// Remove any rotation overlay if present
|
|
247
|
+
if (rotationOverlay != null && rotationOverlay.getParent() != null) {
|
|
248
|
+
((ViewGroup) rotationOverlay.getParent()).removeView(rotationOverlay);
|
|
249
|
+
rotationOverlay = null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (cameraXView != null && cameraXView.isRunning()) {
|
|
253
|
+
cameraXView.stopSession();
|
|
254
|
+
cameraXView = null;
|
|
255
|
+
}
|
|
256
|
+
call.resolve();
|
|
257
|
+
});
|
|
184
258
|
}
|
|
185
259
|
|
|
186
260
|
@PluginMethod
|
|
187
261
|
public void getSupportedFlashModes(PluginCall call) {
|
|
262
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
263
|
+
call.reject("Camera is not running");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
188
266
|
List<String> supportedFlashModes = cameraXView.getSupportedFlashModes();
|
|
189
267
|
JSArray jsonFlashModes = new JSArray();
|
|
190
268
|
for (String mode : supportedFlashModes) {
|
|
@@ -208,7 +286,9 @@ public class CameraPreview
|
|
|
208
286
|
|
|
209
287
|
@PluginMethod
|
|
210
288
|
public void getAvailableDevices(PluginCall call) {
|
|
211
|
-
List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
|
|
289
|
+
List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
|
|
290
|
+
getContext()
|
|
291
|
+
);
|
|
212
292
|
JSArray devicesArray = new JSArray();
|
|
213
293
|
for (CameraDevice device : devices) {
|
|
214
294
|
JSObject deviceJson = new JSObject();
|
|
@@ -236,6 +316,10 @@ public class CameraPreview
|
|
|
236
316
|
|
|
237
317
|
@PluginMethod
|
|
238
318
|
public void getZoom(PluginCall call) {
|
|
319
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
320
|
+
call.reject("Camera is not running");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
239
323
|
ZoomFactors zoomFactors = cameraXView.getZoomFactors();
|
|
240
324
|
JSObject result = new JSObject();
|
|
241
325
|
result.put("min", zoomFactors.getMin());
|
|
@@ -244,6 +328,57 @@ public class CameraPreview
|
|
|
244
328
|
call.resolve(result);
|
|
245
329
|
}
|
|
246
330
|
|
|
331
|
+
@PluginMethod
|
|
332
|
+
public void getZoomButtonValues(PluginCall call) {
|
|
333
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
334
|
+
call.reject("Camera is not running");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Build a sorted set to dedupe and order ascending
|
|
338
|
+
java.util.Set<Double> sorted = new java.util.TreeSet<>();
|
|
339
|
+
sorted.add(1.0);
|
|
340
|
+
sorted.add(2.0);
|
|
341
|
+
|
|
342
|
+
// Try to detect ultra-wide to include its min zoom (often 0.5)
|
|
343
|
+
try {
|
|
344
|
+
List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
|
|
345
|
+
getContext()
|
|
346
|
+
);
|
|
347
|
+
ZoomFactors zoomFactors = cameraXView.getZoomFactors();
|
|
348
|
+
boolean hasUltraWide = false;
|
|
349
|
+
boolean hasTelephoto = false;
|
|
350
|
+
float minUltra = 0.5f;
|
|
351
|
+
|
|
352
|
+
for (CameraDevice device : devices) {
|
|
353
|
+
for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
|
|
354
|
+
if ("ultraWide".equals(lens.getDeviceType())) {
|
|
355
|
+
hasUltraWide = true;
|
|
356
|
+
// Use overall minZoom for that device as the button value to represent UW
|
|
357
|
+
minUltra = Math.max(minUltra, zoomFactors.getMin());
|
|
358
|
+
} else if ("telephoto".equals(lens.getDeviceType())) {
|
|
359
|
+
hasTelephoto = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (hasUltraWide) {
|
|
364
|
+
sorted.add((double) minUltra);
|
|
365
|
+
}
|
|
366
|
+
if (hasTelephoto) {
|
|
367
|
+
sorted.add(3.0);
|
|
368
|
+
}
|
|
369
|
+
} catch (Exception ignored) {
|
|
370
|
+
// Ignore and keep defaults
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
JSObject result = new JSObject();
|
|
374
|
+
JSArray values = new JSArray();
|
|
375
|
+
for (Double v : sorted) {
|
|
376
|
+
values.put(v);
|
|
377
|
+
}
|
|
378
|
+
result.put("values", values);
|
|
379
|
+
call.resolve(result);
|
|
380
|
+
}
|
|
381
|
+
|
|
247
382
|
@PluginMethod
|
|
248
383
|
public void setZoom(PluginCall call) {
|
|
249
384
|
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
@@ -263,6 +398,35 @@ public class CameraPreview
|
|
|
263
398
|
}
|
|
264
399
|
}
|
|
265
400
|
|
|
401
|
+
@PluginMethod
|
|
402
|
+
public void setFocus(PluginCall call) {
|
|
403
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
404
|
+
call.reject("Camera is not running");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
Float x = call.getFloat("x");
|
|
408
|
+
Float y = call.getFloat("y");
|
|
409
|
+
if (x == null || y == null) {
|
|
410
|
+
call.reject("x and y parameters are required");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// Reject if values are outside 0-1 range
|
|
414
|
+
if (x < 0f || x > 1f || y < 0f || y > 1f) {
|
|
415
|
+
call.reject("Focus coordinates must be between 0 and 1");
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getActivity()
|
|
420
|
+
.runOnUiThread(() -> {
|
|
421
|
+
try {
|
|
422
|
+
cameraXView.setFocus(x, y);
|
|
423
|
+
call.resolve();
|
|
424
|
+
} catch (Exception e) {
|
|
425
|
+
call.reject("Failed to set focus: " + e.getMessage());
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
266
430
|
@PluginMethod
|
|
267
431
|
public void setDeviceId(PluginCall call) {
|
|
268
432
|
String deviceId = call.getString("deviceId");
|
|
@@ -285,7 +449,7 @@ public class CameraPreview
|
|
|
285
449
|
JSObject rear = new JSObject();
|
|
286
450
|
rear.put("facing", "rear");
|
|
287
451
|
JSArray rearSizesJs = new JSArray();
|
|
288
|
-
for(Size size : rearSizes) {
|
|
452
|
+
for (Size size : rearSizes) {
|
|
289
453
|
JSObject sizeJs = new JSObject();
|
|
290
454
|
sizeJs.put("width", size.getWidth());
|
|
291
455
|
sizeJs.put("height", size.getHeight());
|
|
@@ -298,7 +462,7 @@ public class CameraPreview
|
|
|
298
462
|
JSObject front = new JSObject();
|
|
299
463
|
front.put("facing", "front");
|
|
300
464
|
JSArray frontSizesJs = new JSArray();
|
|
301
|
-
for(Size size : frontSizes) {
|
|
465
|
+
for (Size size : frontSizes) {
|
|
302
466
|
JSObject sizeJs = new JSObject();
|
|
303
467
|
sizeJs.put("width", size.getWidth());
|
|
304
468
|
sizeJs.put("height", size.getHeight());
|
|
@@ -365,8 +529,14 @@ public class CameraPreview
|
|
|
365
529
|
|
|
366
530
|
@PermissionCallback
|
|
367
531
|
private void handleCameraPermissionResult(PluginCall call) {
|
|
368
|
-
if (
|
|
369
|
-
|
|
532
|
+
if (
|
|
533
|
+
PermissionState.GRANTED.equals(
|
|
534
|
+
getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)
|
|
535
|
+
) ||
|
|
536
|
+
PermissionState.GRANTED.equals(
|
|
537
|
+
getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS)
|
|
538
|
+
)
|
|
539
|
+
) {
|
|
370
540
|
startCamera(call);
|
|
371
541
|
} else {
|
|
372
542
|
call.reject("Permission failed");
|
|
@@ -378,108 +548,706 @@ public class CameraPreview
|
|
|
378
548
|
String originalDeviceId = call.getString("deviceId");
|
|
379
549
|
String deviceId = originalDeviceId; // Use a mutable variable
|
|
380
550
|
|
|
381
|
-
final String position = (positionParam == null ||
|
|
382
|
-
|
|
383
|
-
|
|
551
|
+
final String position = (positionParam == null ||
|
|
552
|
+
positionParam.isEmpty() ||
|
|
553
|
+
"rear".equals(positionParam) ||
|
|
554
|
+
"back".equals(positionParam))
|
|
555
|
+
? "back"
|
|
556
|
+
: "front";
|
|
557
|
+
// Use -1 as default to indicate centering is needed when x/y not provided
|
|
558
|
+
final Integer xParam = call.getInt("x");
|
|
559
|
+
final Integer yParam = call.getInt("y");
|
|
560
|
+
final int x = xParam != null ? xParam : -1;
|
|
561
|
+
final int y = yParam != null ? yParam : -1;
|
|
562
|
+
|
|
563
|
+
Log.d("CameraPreview", "========================");
|
|
564
|
+
Log.d("CameraPreview", "CAMERA POSITION TRACKING START:");
|
|
565
|
+
Log.d(
|
|
566
|
+
"CameraPreview",
|
|
567
|
+
"1. RAW PARAMS - xParam: " + xParam + ", yParam: " + yParam
|
|
568
|
+
);
|
|
569
|
+
Log.d(
|
|
570
|
+
"CameraPreview",
|
|
571
|
+
"2. AFTER DEFAULT - x: " +
|
|
572
|
+
x +
|
|
573
|
+
" (center=" +
|
|
574
|
+
(x == -1) +
|
|
575
|
+
"), y: " +
|
|
576
|
+
y +
|
|
577
|
+
" (center=" +
|
|
578
|
+
(y == -1) +
|
|
579
|
+
")"
|
|
580
|
+
);
|
|
384
581
|
final int width = call.getInt("width", 0);
|
|
385
582
|
final int height = call.getInt("height", 0);
|
|
386
583
|
final int paddingBottom = call.getInt("paddingBottom", 0);
|
|
387
584
|
final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
|
|
388
|
-
final boolean storeToFile = Boolean.TRUE.equals(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
final boolean
|
|
392
|
-
|
|
393
|
-
|
|
585
|
+
final boolean storeToFile = Boolean.TRUE.equals(
|
|
586
|
+
call.getBoolean("storeToFile", false)
|
|
587
|
+
);
|
|
588
|
+
final boolean enableOpacity = Boolean.TRUE.equals(
|
|
589
|
+
call.getBoolean("enableOpacity", false)
|
|
590
|
+
);
|
|
591
|
+
final boolean enableZoom = Boolean.TRUE.equals(
|
|
592
|
+
call.getBoolean("enableZoom", false)
|
|
593
|
+
);
|
|
594
|
+
final boolean disableExifHeaderStripping = Boolean.TRUE.equals(
|
|
595
|
+
call.getBoolean("disableExifHeaderStripping", false)
|
|
596
|
+
);
|
|
597
|
+
final boolean lockOrientation = Boolean.TRUE.equals(
|
|
598
|
+
call.getBoolean("lockAndroidOrientation", false)
|
|
599
|
+
);
|
|
600
|
+
final boolean disableAudio = Boolean.TRUE.equals(
|
|
601
|
+
call.getBoolean("disableAudio", true)
|
|
602
|
+
);
|
|
394
603
|
final String aspectRatio = call.getString("aspectRatio", "4:3");
|
|
395
604
|
final String gridMode = call.getString("gridMode", "none");
|
|
396
|
-
|
|
605
|
+
final String positioning = call.getString("positioning", "top");
|
|
606
|
+
final float initialZoomLevel = call.getFloat("initialZoomLevel", 1.0f);
|
|
607
|
+
|
|
397
608
|
// Check for conflict between aspectRatio and size
|
|
398
|
-
if (
|
|
399
|
-
call.
|
|
609
|
+
if (
|
|
610
|
+
call.getData().has("aspectRatio") &&
|
|
611
|
+
(call.getData().has("width") || call.getData().has("height"))
|
|
612
|
+
) {
|
|
613
|
+
call.reject(
|
|
614
|
+
"Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start."
|
|
615
|
+
);
|
|
400
616
|
return;
|
|
401
617
|
}
|
|
402
618
|
|
|
403
|
-
float targetZoom =
|
|
619
|
+
float targetZoom = initialZoomLevel;
|
|
404
620
|
// Check if the selected device is a physical ultra-wide
|
|
405
621
|
if (originalDeviceId != null) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
622
|
+
List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
|
|
623
|
+
getContext()
|
|
624
|
+
);
|
|
625
|
+
for (CameraDevice device : devices) {
|
|
626
|
+
if (
|
|
627
|
+
originalDeviceId.equals(device.getDeviceId()) && !device.isLogical()
|
|
628
|
+
) {
|
|
629
|
+
for (LensInfo lens : device.getLenses()) {
|
|
630
|
+
if ("ultraWide".equals(lens.getDeviceType())) {
|
|
631
|
+
Log.d(
|
|
632
|
+
"CameraPreview",
|
|
633
|
+
"Ultra-wide lens selected. Targeting 0.5x zoom on logical camera."
|
|
634
|
+
);
|
|
635
|
+
targetZoom = 0.5f;
|
|
636
|
+
// Force the use of the logical camera by clearing the specific deviceId
|
|
637
|
+
deviceId = null;
|
|
638
|
+
break;
|
|
418
639
|
}
|
|
419
|
-
|
|
640
|
+
}
|
|
420
641
|
}
|
|
642
|
+
if (deviceId == null) break; // Exit outer loop once we've made our decision
|
|
643
|
+
}
|
|
421
644
|
}
|
|
422
645
|
|
|
423
|
-
previousOrientationRequest = getBridge()
|
|
646
|
+
previousOrientationRequest = getBridge()
|
|
647
|
+
.getActivity()
|
|
648
|
+
.getRequestedOrientation();
|
|
424
649
|
cameraXView = new CameraXView(getContext(), getBridge().getWebView());
|
|
425
650
|
cameraXView.setListener(this);
|
|
426
651
|
|
|
427
652
|
String finalDeviceId = deviceId;
|
|
428
653
|
float finalTargetZoom = targetZoom;
|
|
429
|
-
getBridge()
|
|
430
|
-
|
|
654
|
+
getBridge()
|
|
655
|
+
.getActivity()
|
|
656
|
+
.runOnUiThread(() -> {
|
|
657
|
+
DisplayMetrics metrics = getBridge()
|
|
658
|
+
.getActivity()
|
|
659
|
+
.getResources()
|
|
660
|
+
.getDisplayMetrics();
|
|
431
661
|
if (lockOrientation) {
|
|
432
|
-
getBridge()
|
|
662
|
+
getBridge()
|
|
663
|
+
.getActivity()
|
|
664
|
+
.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
|
|
433
665
|
}
|
|
434
|
-
|
|
666
|
+
|
|
435
667
|
// Debug: Let's check all the positioning information
|
|
436
|
-
ViewGroup webViewParent = (ViewGroup) getBridge()
|
|
437
|
-
|
|
668
|
+
ViewGroup webViewParent = (ViewGroup) getBridge()
|
|
669
|
+
.getWebView()
|
|
670
|
+
.getParent();
|
|
671
|
+
|
|
438
672
|
// Get webview position in different coordinate systems
|
|
439
673
|
int[] webViewLocationInWindow = new int[2];
|
|
440
674
|
int[] webViewLocationOnScreen = new int[2];
|
|
441
675
|
getBridge().getWebView().getLocationInWindow(webViewLocationInWindow);
|
|
442
676
|
getBridge().getWebView().getLocationOnScreen(webViewLocationOnScreen);
|
|
443
|
-
|
|
677
|
+
|
|
444
678
|
int webViewLeft = getBridge().getWebView().getLeft();
|
|
445
679
|
int webViewTop = getBridge().getWebView().getTop();
|
|
446
|
-
|
|
680
|
+
|
|
447
681
|
// Check parent position too
|
|
448
682
|
int[] parentLocationInWindow = new int[2];
|
|
449
683
|
int[] parentLocationOnScreen = new int[2];
|
|
450
684
|
webViewParent.getLocationInWindow(parentLocationInWindow);
|
|
451
685
|
webViewParent.getLocationOnScreen(parentLocationOnScreen);
|
|
452
|
-
|
|
686
|
+
|
|
453
687
|
// Calculate pixel ratio
|
|
454
688
|
float pixelRatio = metrics.density;
|
|
455
|
-
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
689
|
+
|
|
690
|
+
// The key insight: JavaScript coordinates are relative to the WebView's viewport
|
|
691
|
+
// If the WebView is positioned below the status bar (webViewLocationOnScreen[1] > 0),
|
|
692
|
+
// we need to add that offset when placing native views
|
|
693
|
+
int webViewTopInset = webViewLocationOnScreen[1];
|
|
694
|
+
boolean isEdgeToEdgeActive = webViewLocationOnScreen[1] > 0;
|
|
695
|
+
|
|
696
|
+
// Log all the positioning information for debugging
|
|
697
|
+
Log.d("CameraPreview", "WebView Position Debug:");
|
|
698
|
+
Log.d("CameraPreview", " - webView.getTop(): " + webViewTop);
|
|
699
|
+
Log.d("CameraPreview", " - webView.getLeft(): " + webViewLeft);
|
|
700
|
+
Log.d(
|
|
701
|
+
"CameraPreview",
|
|
702
|
+
" - webView locationInWindow: (" +
|
|
703
|
+
webViewLocationInWindow[0] +
|
|
704
|
+
", " +
|
|
705
|
+
webViewLocationInWindow[1] +
|
|
706
|
+
")"
|
|
707
|
+
);
|
|
708
|
+
Log.d(
|
|
709
|
+
"CameraPreview",
|
|
710
|
+
" - webView locationOnScreen: (" +
|
|
711
|
+
webViewLocationOnScreen[0] +
|
|
712
|
+
", " +
|
|
713
|
+
webViewLocationOnScreen[1] +
|
|
714
|
+
")"
|
|
715
|
+
);
|
|
716
|
+
Log.d(
|
|
717
|
+
"CameraPreview",
|
|
718
|
+
" - parent locationInWindow: (" +
|
|
719
|
+
parentLocationInWindow[0] +
|
|
720
|
+
", " +
|
|
721
|
+
parentLocationInWindow[1] +
|
|
722
|
+
")"
|
|
723
|
+
);
|
|
724
|
+
Log.d(
|
|
725
|
+
"CameraPreview",
|
|
726
|
+
" - parent locationOnScreen: (" +
|
|
727
|
+
parentLocationOnScreen[0] +
|
|
728
|
+
", " +
|
|
729
|
+
parentLocationOnScreen[1] +
|
|
730
|
+
")"
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
// Check if WebView has margins
|
|
734
|
+
View webView = getBridge().getWebView();
|
|
735
|
+
ViewGroup.LayoutParams webViewLayoutParams = webView.getLayoutParams();
|
|
736
|
+
if (webViewLayoutParams instanceof ViewGroup.MarginLayoutParams) {
|
|
737
|
+
ViewGroup.MarginLayoutParams marginParams =
|
|
738
|
+
(ViewGroup.MarginLayoutParams) webViewLayoutParams;
|
|
739
|
+
Log.d(
|
|
740
|
+
"CameraPreview",
|
|
741
|
+
" - webView margins: left=" +
|
|
742
|
+
marginParams.leftMargin +
|
|
743
|
+
", top=" +
|
|
744
|
+
marginParams.topMargin +
|
|
745
|
+
", right=" +
|
|
746
|
+
marginParams.rightMargin +
|
|
747
|
+
", bottom=" +
|
|
748
|
+
marginParams.bottomMargin
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Check WebView padding
|
|
753
|
+
Log.d(
|
|
754
|
+
"CameraPreview",
|
|
755
|
+
" - webView padding: left=" +
|
|
756
|
+
webView.getPaddingLeft() +
|
|
757
|
+
", top=" +
|
|
758
|
+
webView.getPaddingTop() +
|
|
759
|
+
", right=" +
|
|
760
|
+
webView.getPaddingRight() +
|
|
761
|
+
", bottom=" +
|
|
762
|
+
webView.getPaddingBottom()
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
Log.d("CameraPreview", " - Using webViewTopInset: " + webViewTopInset);
|
|
766
|
+
Log.d("CameraPreview", " - isEdgeToEdgeActive: " + isEdgeToEdgeActive);
|
|
767
|
+
|
|
768
|
+
// Calculate position - center if x or y is -1
|
|
769
|
+
int computedX;
|
|
770
|
+
int computedY;
|
|
771
|
+
|
|
772
|
+
// Calculate dimensions first
|
|
773
|
+
int computedWidth = width != 0
|
|
774
|
+
? (int) (width * pixelRatio)
|
|
775
|
+
: getBridge().getWebView().getWidth();
|
|
776
|
+
int computedHeight = height != 0
|
|
777
|
+
? (int) (height * pixelRatio)
|
|
778
|
+
: getBridge().getWebView().getHeight();
|
|
779
|
+
computedHeight -= (int) (paddingBottom * pixelRatio);
|
|
780
|
+
|
|
781
|
+
Log.d("CameraPreview", "========================");
|
|
782
|
+
Log.d("CameraPreview", "POSITIONING CALCULATIONS:");
|
|
783
|
+
Log.d(
|
|
784
|
+
"CameraPreview",
|
|
785
|
+
"1. INPUT - x: " +
|
|
786
|
+
x +
|
|
787
|
+
", y: " +
|
|
788
|
+
y +
|
|
789
|
+
", width: " +
|
|
790
|
+
width +
|
|
791
|
+
", height: " +
|
|
792
|
+
height
|
|
793
|
+
);
|
|
794
|
+
Log.d("CameraPreview", "2. PIXEL RATIO: " + pixelRatio);
|
|
795
|
+
Log.d(
|
|
796
|
+
"CameraPreview",
|
|
797
|
+
"3. SCREEN - width: " +
|
|
798
|
+
metrics.widthPixels +
|
|
799
|
+
", height: " +
|
|
800
|
+
metrics.heightPixels
|
|
801
|
+
);
|
|
802
|
+
Log.d(
|
|
803
|
+
"CameraPreview",
|
|
804
|
+
"4. WEBVIEW - width: " +
|
|
805
|
+
getBridge().getWebView().getWidth() +
|
|
806
|
+
", height: " +
|
|
807
|
+
getBridge().getWebView().getHeight()
|
|
808
|
+
);
|
|
809
|
+
Log.d(
|
|
810
|
+
"CameraPreview",
|
|
811
|
+
"5. COMPUTED DIMENSIONS - width: " +
|
|
812
|
+
computedWidth +
|
|
813
|
+
", height: " +
|
|
814
|
+
computedHeight
|
|
815
|
+
);
|
|
816
|
+
|
|
817
|
+
if (x == -1) {
|
|
818
|
+
// Center horizontally
|
|
819
|
+
int screenWidth = metrics.widthPixels;
|
|
820
|
+
computedX = (screenWidth - computedWidth) / 2;
|
|
821
|
+
Log.d(
|
|
822
|
+
"CameraPreview",
|
|
823
|
+
"Centering horizontally: screenWidth=" +
|
|
824
|
+
screenWidth +
|
|
825
|
+
", computedWidth=" +
|
|
826
|
+
computedWidth +
|
|
827
|
+
", computedX=" +
|
|
828
|
+
computedX
|
|
829
|
+
);
|
|
830
|
+
} else {
|
|
831
|
+
computedX = (int) (x * pixelRatio);
|
|
832
|
+
Log.d(
|
|
833
|
+
"CameraPreview",
|
|
834
|
+
"Using provided X position: " +
|
|
835
|
+
x +
|
|
836
|
+
" * " +
|
|
837
|
+
pixelRatio +
|
|
838
|
+
" = " +
|
|
839
|
+
computedX
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (y == -1) {
|
|
844
|
+
// Position vertically based on positioning parameter
|
|
845
|
+
int screenHeight = metrics.heightPixels;
|
|
846
|
+
|
|
847
|
+
switch (positioning) {
|
|
848
|
+
case "top":
|
|
849
|
+
computedY = 0;
|
|
850
|
+
Log.d("CameraPreview", "Positioning at top: computedY=0");
|
|
851
|
+
break;
|
|
852
|
+
case "bottom":
|
|
853
|
+
computedY = screenHeight - computedHeight;
|
|
854
|
+
Log.d(
|
|
855
|
+
"CameraPreview",
|
|
856
|
+
"Positioning at bottom: screenHeight=" +
|
|
857
|
+
screenHeight +
|
|
858
|
+
", computedHeight=" +
|
|
859
|
+
computedHeight +
|
|
860
|
+
", computedY=" +
|
|
861
|
+
computedY
|
|
862
|
+
);
|
|
863
|
+
break;
|
|
864
|
+
case "center":
|
|
865
|
+
default:
|
|
866
|
+
// Center vertically
|
|
867
|
+
if (isEdgeToEdgeActive) {
|
|
868
|
+
// When WebView is offset from top, center within the available space
|
|
869
|
+
// The camera should be centered in the full screen, not just the WebView area
|
|
870
|
+
computedY = (screenHeight - computedHeight) / 2;
|
|
871
|
+
Log.d(
|
|
872
|
+
"CameraPreview",
|
|
873
|
+
"Centering vertically with WebView offset: screenHeight=" +
|
|
874
|
+
screenHeight +
|
|
875
|
+
", webViewTop=" +
|
|
876
|
+
webViewTopInset +
|
|
877
|
+
", computedHeight=" +
|
|
878
|
+
computedHeight +
|
|
879
|
+
", computedY=" +
|
|
880
|
+
computedY
|
|
881
|
+
);
|
|
882
|
+
} else {
|
|
883
|
+
// Normal mode - use full screen height
|
|
884
|
+
computedY = (screenHeight - computedHeight) / 2;
|
|
885
|
+
Log.d(
|
|
886
|
+
"CameraPreview",
|
|
887
|
+
"Centering vertically (normal): screenHeight=" +
|
|
888
|
+
screenHeight +
|
|
889
|
+
", computedHeight=" +
|
|
890
|
+
computedHeight +
|
|
891
|
+
", computedY=" +
|
|
892
|
+
computedY
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
computedY = (int) (y * pixelRatio);
|
|
899
|
+
// If edge-to-edge is active, JavaScript Y is relative to WebView content area
|
|
900
|
+
// We need to add the inset to get absolute screen position
|
|
901
|
+
if (isEdgeToEdgeActive) {
|
|
902
|
+
computedY += webViewTopInset;
|
|
903
|
+
Log.d(
|
|
904
|
+
"CameraPreview",
|
|
905
|
+
"Edge-to-edge adjustment: Y position " +
|
|
906
|
+
(int) (y * pixelRatio) +
|
|
907
|
+
" + inset " +
|
|
908
|
+
webViewTopInset +
|
|
909
|
+
" = " +
|
|
910
|
+
computedY
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
Log.d(
|
|
914
|
+
"CameraPreview",
|
|
915
|
+
"Using provided Y position: " +
|
|
916
|
+
y +
|
|
917
|
+
" * " +
|
|
918
|
+
pixelRatio +
|
|
919
|
+
" = " +
|
|
920
|
+
computedY +
|
|
921
|
+
(isEdgeToEdgeActive ? " (adjusted for edge-to-edge)" : "")
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
Log.d(
|
|
926
|
+
"CameraPreview",
|
|
927
|
+
"2b. EDGE-TO-EDGE - " +
|
|
928
|
+
(isEdgeToEdgeActive
|
|
929
|
+
? "ACTIVE (inset=" + webViewTopInset + ")"
|
|
930
|
+
: "INACTIVE")
|
|
931
|
+
);
|
|
932
|
+
Log.d(
|
|
933
|
+
"CameraPreview",
|
|
934
|
+
"3. COMPUTED POSITION - x=" + computedX + ", y=" + computedY
|
|
935
|
+
);
|
|
936
|
+
Log.d(
|
|
937
|
+
"CameraPreview",
|
|
938
|
+
"4. COMPUTED SIZE - width=" +
|
|
939
|
+
computedWidth +
|
|
940
|
+
", height=" +
|
|
941
|
+
computedHeight
|
|
942
|
+
);
|
|
460
943
|
Log.d("CameraPreview", "=== COORDINATE DEBUG ===");
|
|
461
|
-
Log.d(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
Log.d(
|
|
466
|
-
|
|
467
|
-
|
|
944
|
+
Log.d(
|
|
945
|
+
"CameraPreview",
|
|
946
|
+
"WebView getLeft/getTop: (" + webViewLeft + ", " + webViewTop + ")"
|
|
947
|
+
);
|
|
948
|
+
Log.d(
|
|
949
|
+
"CameraPreview",
|
|
950
|
+
"WebView locationInWindow: (" +
|
|
951
|
+
webViewLocationInWindow[0] +
|
|
952
|
+
", " +
|
|
953
|
+
webViewLocationInWindow[1] +
|
|
954
|
+
")"
|
|
955
|
+
);
|
|
956
|
+
Log.d(
|
|
957
|
+
"CameraPreview",
|
|
958
|
+
"WebView locationOnScreen: (" +
|
|
959
|
+
webViewLocationOnScreen[0] +
|
|
960
|
+
", " +
|
|
961
|
+
webViewLocationOnScreen[1] +
|
|
962
|
+
")"
|
|
963
|
+
);
|
|
964
|
+
Log.d(
|
|
965
|
+
"CameraPreview",
|
|
966
|
+
"Parent locationInWindow: (" +
|
|
967
|
+
parentLocationInWindow[0] +
|
|
968
|
+
", " +
|
|
969
|
+
parentLocationInWindow[1] +
|
|
970
|
+
")"
|
|
971
|
+
);
|
|
972
|
+
Log.d(
|
|
973
|
+
"CameraPreview",
|
|
974
|
+
"Parent locationOnScreen: (" +
|
|
975
|
+
parentLocationOnScreen[0] +
|
|
976
|
+
", " +
|
|
977
|
+
parentLocationOnScreen[1] +
|
|
978
|
+
")"
|
|
979
|
+
);
|
|
980
|
+
Log.d(
|
|
981
|
+
"CameraPreview",
|
|
982
|
+
"Parent class: " + webViewParent.getClass().getSimpleName()
|
|
983
|
+
);
|
|
984
|
+
Log.d(
|
|
985
|
+
"CameraPreview",
|
|
986
|
+
"Requested position (logical): (" + x + ", " + y + ")"
|
|
987
|
+
);
|
|
468
988
|
Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
|
|
469
|
-
Log.d(
|
|
989
|
+
Log.d(
|
|
990
|
+
"CameraPreview",
|
|
991
|
+
"Final computed position (no offset): (" +
|
|
992
|
+
computedX +
|
|
993
|
+
", " +
|
|
994
|
+
computedY +
|
|
995
|
+
")"
|
|
996
|
+
);
|
|
997
|
+
Log.d("CameraPreview", "5. IS_CENTERED - " + (x == -1 || y == -1));
|
|
470
998
|
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
999
|
|
|
475
|
-
|
|
1000
|
+
// Pass along whether we're centering so CameraXView knows not to add insets
|
|
1001
|
+
boolean isCentered = (x == -1 || y == -1);
|
|
1002
|
+
|
|
1003
|
+
CameraSessionConfiguration config = new CameraSessionConfiguration(
|
|
1004
|
+
finalDeviceId,
|
|
1005
|
+
position,
|
|
1006
|
+
computedX,
|
|
1007
|
+
computedY,
|
|
1008
|
+
computedWidth,
|
|
1009
|
+
computedHeight,
|
|
1010
|
+
paddingBottom,
|
|
1011
|
+
toBack,
|
|
1012
|
+
storeToFile,
|
|
1013
|
+
enableOpacity,
|
|
1014
|
+
enableZoom,
|
|
1015
|
+
disableExifHeaderStripping,
|
|
1016
|
+
disableAudio,
|
|
1017
|
+
1.0f,
|
|
1018
|
+
aspectRatio,
|
|
1019
|
+
gridMode
|
|
1020
|
+
);
|
|
476
1021
|
config.setTargetZoom(finalTargetZoom);
|
|
1022
|
+
config.setCentered(isCentered);
|
|
477
1023
|
|
|
478
1024
|
bridge.saveCall(call);
|
|
479
1025
|
cameraStartCallbackId = call.getCallbackId();
|
|
480
1026
|
cameraXView.startSession(config);
|
|
481
|
-
|
|
1027
|
+
|
|
1028
|
+
// Setup orientation listener to mirror iOS screenResize emission
|
|
1029
|
+
if (orientationListener == null) {
|
|
1030
|
+
lastOrientation = getContext()
|
|
1031
|
+
.getResources()
|
|
1032
|
+
.getConfiguration()
|
|
1033
|
+
.orientation;
|
|
1034
|
+
orientationListener = new OrientationEventListener(getContext()) {
|
|
1035
|
+
@Override
|
|
1036
|
+
public void onOrientationChanged(int orientation) {
|
|
1037
|
+
if (orientation == ORIENTATION_UNKNOWN) return;
|
|
1038
|
+
int current = getContext()
|
|
1039
|
+
.getResources()
|
|
1040
|
+
.getConfiguration()
|
|
1041
|
+
.orientation;
|
|
1042
|
+
if (current != lastOrientation) {
|
|
1043
|
+
lastOrientation = current;
|
|
1044
|
+
// Post to next frame so WebView has updated bounds before we recompute layout
|
|
1045
|
+
getBridge()
|
|
1046
|
+
.getActivity()
|
|
1047
|
+
.getWindow()
|
|
1048
|
+
.getDecorView()
|
|
1049
|
+
.post(() -> handleOrientationChange());
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
if (orientationListener.canDetectOrientation()) {
|
|
1054
|
+
orientationListener.enable();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
private void handleOrientationChange() {
|
|
1061
|
+
if (cameraXView == null || !cameraXView.isRunning()) return;
|
|
1062
|
+
|
|
1063
|
+
Log.d(
|
|
1064
|
+
TAG,
|
|
1065
|
+
"======================== ORIENTATION CHANGE DETECTED ========================"
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
// Get comprehensive display and orientation information
|
|
1069
|
+
android.util.DisplayMetrics metrics = getContext()
|
|
1070
|
+
.getResources()
|
|
1071
|
+
.getDisplayMetrics();
|
|
1072
|
+
int screenWidthPx = metrics.widthPixels;
|
|
1073
|
+
int screenHeightPx = metrics.heightPixels;
|
|
1074
|
+
float density = metrics.density;
|
|
1075
|
+
int screenWidthDp = (int) (screenWidthPx / density);
|
|
1076
|
+
int screenHeightDp = (int) (screenHeightPx / density);
|
|
1077
|
+
|
|
1078
|
+
int current = getContext().getResources().getConfiguration().orientation;
|
|
1079
|
+
Log.d(TAG, "New orientation: " + current + " (1=PORTRAIT, 2=LANDSCAPE)");
|
|
1080
|
+
Log.d(
|
|
1081
|
+
TAG,
|
|
1082
|
+
"Screen dimensions - Pixels: " +
|
|
1083
|
+
screenWidthPx +
|
|
1084
|
+
"x" +
|
|
1085
|
+
screenHeightPx +
|
|
1086
|
+
", DP: " +
|
|
1087
|
+
screenWidthDp +
|
|
1088
|
+
"x" +
|
|
1089
|
+
screenHeightDp +
|
|
1090
|
+
", Density: " +
|
|
1091
|
+
density
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
// Get WebView dimensions before rotation
|
|
1095
|
+
WebView webView = getBridge().getWebView();
|
|
1096
|
+
int webViewWidth = webView.getWidth();
|
|
1097
|
+
int webViewHeight = webView.getHeight();
|
|
1098
|
+
Log.d(TAG, "WebView dimensions: " + webViewWidth + "x" + webViewHeight);
|
|
1099
|
+
|
|
1100
|
+
// Get current preview bounds before rotation
|
|
1101
|
+
int[] oldBounds = cameraXView.getCurrentPreviewBounds();
|
|
1102
|
+
Log.d(
|
|
1103
|
+
TAG,
|
|
1104
|
+
"Current preview bounds before rotation: x=" +
|
|
1105
|
+
oldBounds[0] +
|
|
1106
|
+
", y=" +
|
|
1107
|
+
oldBounds[1] +
|
|
1108
|
+
", width=" +
|
|
1109
|
+
oldBounds[2] +
|
|
1110
|
+
", height=" +
|
|
1111
|
+
oldBounds[3]
|
|
482
1112
|
);
|
|
1113
|
+
|
|
1114
|
+
getBridge()
|
|
1115
|
+
.getActivity()
|
|
1116
|
+
.runOnUiThread(() -> {
|
|
1117
|
+
// Create and show a black full-screen overlay during rotation
|
|
1118
|
+
ViewGroup rootView = (ViewGroup) getBridge()
|
|
1119
|
+
.getActivity()
|
|
1120
|
+
.getWindow()
|
|
1121
|
+
.getDecorView()
|
|
1122
|
+
.getRootView();
|
|
1123
|
+
|
|
1124
|
+
// Remove any existing overlay
|
|
1125
|
+
if (rotationOverlay != null && rotationOverlay.getParent() != null) {
|
|
1126
|
+
((ViewGroup) rotationOverlay.getParent()).removeView(rotationOverlay);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Create new black overlay
|
|
1130
|
+
rotationOverlay = new View(getContext());
|
|
1131
|
+
rotationOverlay.setBackgroundColor(Color.BLACK);
|
|
1132
|
+
ViewGroup.LayoutParams overlayParams = new ViewGroup.LayoutParams(
|
|
1133
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1134
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
1135
|
+
);
|
|
1136
|
+
rotationOverlay.setLayoutParams(overlayParams);
|
|
1137
|
+
rootView.addView(rotationOverlay);
|
|
1138
|
+
|
|
1139
|
+
// Reapply current aspect ratio to recompute layout, then emit screenResize
|
|
1140
|
+
String ar = cameraXView.getAspectRatio();
|
|
1141
|
+
Log.d(TAG, "Reapplying aspect ratio: " + ar);
|
|
1142
|
+
|
|
1143
|
+
// Re-get dimensions after potential layout pass
|
|
1144
|
+
android.util.DisplayMetrics newMetrics = getContext()
|
|
1145
|
+
.getResources()
|
|
1146
|
+
.getDisplayMetrics();
|
|
1147
|
+
int newScreenWidthPx = newMetrics.widthPixels;
|
|
1148
|
+
int newScreenHeightPx = newMetrics.heightPixels;
|
|
1149
|
+
int newWebViewWidth = webView.getWidth();
|
|
1150
|
+
int newWebViewHeight = webView.getHeight();
|
|
1151
|
+
|
|
1152
|
+
Log.d(
|
|
1153
|
+
TAG,
|
|
1154
|
+
"New screen dimensions after rotation: " +
|
|
1155
|
+
newScreenWidthPx +
|
|
1156
|
+
"x" +
|
|
1157
|
+
newScreenHeightPx
|
|
1158
|
+
);
|
|
1159
|
+
Log.d(
|
|
1160
|
+
TAG,
|
|
1161
|
+
"New WebView dimensions after rotation: " +
|
|
1162
|
+
newWebViewWidth +
|
|
1163
|
+
"x" +
|
|
1164
|
+
newWebViewHeight
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
// Force aspect ratio recalculation on orientation change
|
|
1168
|
+
cameraXView.forceAspectRatioRecalculation(ar, null, null, () -> {
|
|
1169
|
+
int[] bounds = cameraXView.getCurrentPreviewBounds();
|
|
1170
|
+
Log.d(
|
|
1171
|
+
TAG,
|
|
1172
|
+
"New bounds after orientation change: x=" +
|
|
1173
|
+
bounds[0] +
|
|
1174
|
+
", y=" +
|
|
1175
|
+
bounds[1] +
|
|
1176
|
+
", width=" +
|
|
1177
|
+
bounds[2] +
|
|
1178
|
+
", height=" +
|
|
1179
|
+
bounds[3]
|
|
1180
|
+
);
|
|
1181
|
+
Log.d(
|
|
1182
|
+
TAG,
|
|
1183
|
+
"Bounds change: deltaX=" +
|
|
1184
|
+
(bounds[0] - oldBounds[0]) +
|
|
1185
|
+
", deltaY=" +
|
|
1186
|
+
(bounds[1] - oldBounds[1]) +
|
|
1187
|
+
", deltaWidth=" +
|
|
1188
|
+
(bounds[2] - oldBounds[2]) +
|
|
1189
|
+
", deltaHeight=" +
|
|
1190
|
+
(bounds[3] - oldBounds[3])
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
JSObject data = new JSObject();
|
|
1194
|
+
data.put("x", bounds[0]);
|
|
1195
|
+
data.put("y", bounds[1]);
|
|
1196
|
+
data.put("width", bounds[2]);
|
|
1197
|
+
data.put("height", bounds[3]);
|
|
1198
|
+
notifyListeners("screenResize", data);
|
|
1199
|
+
|
|
1200
|
+
// Also emit orientationChange with a unified string value
|
|
1201
|
+
String o;
|
|
1202
|
+
if (current == Configuration.ORIENTATION_PORTRAIT) {
|
|
1203
|
+
o = "portrait";
|
|
1204
|
+
} else if (current == Configuration.ORIENTATION_LANDSCAPE) {
|
|
1205
|
+
o = "landscape";
|
|
1206
|
+
} else {
|
|
1207
|
+
o = "unknown";
|
|
1208
|
+
}
|
|
1209
|
+
JSObject oData = new JSObject();
|
|
1210
|
+
oData.put("orientation", o);
|
|
1211
|
+
notifyListeners("orientationChange", oData);
|
|
1212
|
+
|
|
1213
|
+
// Don't remove the overlay here - wait for camera to fully start
|
|
1214
|
+
// The overlay will be removed after a delay to ensure camera is stable
|
|
1215
|
+
if (rotationOverlay != null && rotationOverlay.getParent() != null) {
|
|
1216
|
+
// Shorter delay for faster transition
|
|
1217
|
+
int delay = "4:3".equals(ar) ? 200 : 150;
|
|
1218
|
+
rotationOverlay.postDelayed(
|
|
1219
|
+
() -> {
|
|
1220
|
+
if (
|
|
1221
|
+
rotationOverlay != null && rotationOverlay.getParent() != null
|
|
1222
|
+
) {
|
|
1223
|
+
rotationOverlay
|
|
1224
|
+
.animate()
|
|
1225
|
+
.alpha(0f)
|
|
1226
|
+
.setDuration(100) // Faster fade out
|
|
1227
|
+
.withEndAction(() -> {
|
|
1228
|
+
if (
|
|
1229
|
+
rotationOverlay != null &&
|
|
1230
|
+
rotationOverlay.getParent() != null
|
|
1231
|
+
) {
|
|
1232
|
+
((ViewGroup) rotationOverlay.getParent()).removeView(
|
|
1233
|
+
rotationOverlay
|
|
1234
|
+
);
|
|
1235
|
+
rotationOverlay = null;
|
|
1236
|
+
}
|
|
1237
|
+
})
|
|
1238
|
+
.start();
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
delay
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
Log.d(
|
|
1246
|
+
TAG,
|
|
1247
|
+
"================================================================================"
|
|
1248
|
+
);
|
|
1249
|
+
});
|
|
1250
|
+
});
|
|
483
1251
|
}
|
|
484
1252
|
|
|
485
1253
|
@Override
|
|
@@ -507,22 +1275,202 @@ public class CameraPreview
|
|
|
507
1275
|
bridge.releaseCall(pluginCall);
|
|
508
1276
|
}
|
|
509
1277
|
|
|
1278
|
+
private JSObject getViewSize(
|
|
1279
|
+
double x,
|
|
1280
|
+
double y,
|
|
1281
|
+
double width,
|
|
1282
|
+
double height
|
|
1283
|
+
) {
|
|
1284
|
+
JSObject ret = new JSObject();
|
|
1285
|
+
// Return values with proper rounding to avoid gaps
|
|
1286
|
+
// For positions (x, y): ceil to avoid gaps at top/left
|
|
1287
|
+
// For dimensions (width, height): floor to avoid gaps at bottom/right
|
|
1288
|
+
ret.put("x", Math.ceil(x));
|
|
1289
|
+
ret.put("y", Math.ceil(y));
|
|
1290
|
+
ret.put("width", Math.floor(width));
|
|
1291
|
+
ret.put("height", Math.floor(height));
|
|
1292
|
+
return ret;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
510
1295
|
@Override
|
|
511
1296
|
public void onCameraStarted(int width, int height, int x, int y) {
|
|
512
1297
|
PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
|
|
513
1298
|
if (call != null) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
1299
|
+
// Convert pixel values back to logical units
|
|
1300
|
+
DisplayMetrics metrics = getBridge()
|
|
1301
|
+
.getActivity()
|
|
1302
|
+
.getResources()
|
|
1303
|
+
.getDisplayMetrics();
|
|
1304
|
+
float pixelRatio = metrics.density;
|
|
1305
|
+
|
|
1306
|
+
// When WebView is offset from the top (e.g., below status bar),
|
|
1307
|
+
// we need to convert between JavaScript coordinates (relative to WebView)
|
|
1308
|
+
// and native coordinates (relative to screen)
|
|
1309
|
+
WebView webView = getBridge().getWebView();
|
|
1310
|
+
int webViewTopInset = 0;
|
|
1311
|
+
boolean isEdgeToEdgeActive = false;
|
|
1312
|
+
if (webView != null) {
|
|
1313
|
+
int[] location = new int[2];
|
|
1314
|
+
webView.getLocationOnScreen(location);
|
|
1315
|
+
webViewTopInset = location[1];
|
|
1316
|
+
isEdgeToEdgeActive = webViewTopInset > 0;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Only convert to relative position if edge-to-edge is active
|
|
1320
|
+
int relativeY = isEdgeToEdgeActive ? (y - webViewTopInset) : y;
|
|
1321
|
+
|
|
1322
|
+
Log.d("CameraPreview", "========================");
|
|
1323
|
+
Log.d("CameraPreview", "CAMERA STARTED - POSITION RETURNED:");
|
|
1324
|
+
Log.d(
|
|
1325
|
+
"CameraPreview",
|
|
1326
|
+
"7. RETURNED (pixels) - x=" +
|
|
1327
|
+
x +
|
|
1328
|
+
", y=" +
|
|
1329
|
+
y +
|
|
1330
|
+
", width=" +
|
|
1331
|
+
width +
|
|
1332
|
+
", height=" +
|
|
1333
|
+
height
|
|
1334
|
+
);
|
|
1335
|
+
Log.d(
|
|
1336
|
+
"CameraPreview",
|
|
1337
|
+
"8. EDGE-TO-EDGE - " + (isEdgeToEdgeActive ? "ACTIVE" : "INACTIVE")
|
|
1338
|
+
);
|
|
1339
|
+
Log.d("CameraPreview", "9. WEBVIEW INSET - " + webViewTopInset);
|
|
1340
|
+
Log.d(
|
|
1341
|
+
"CameraPreview",
|
|
1342
|
+
"10. RELATIVE Y - " +
|
|
1343
|
+
relativeY +
|
|
1344
|
+
" (y=" +
|
|
1345
|
+
y +
|
|
1346
|
+
(isEdgeToEdgeActive ? " - inset=" + webViewTopInset : " unchanged") +
|
|
1347
|
+
")"
|
|
1348
|
+
);
|
|
1349
|
+
Log.d(
|
|
1350
|
+
"CameraPreview",
|
|
1351
|
+
"11. RETURNED (logical) - x=" +
|
|
1352
|
+
(x / pixelRatio) +
|
|
1353
|
+
", y=" +
|
|
1354
|
+
(relativeY / pixelRatio) +
|
|
1355
|
+
", width=" +
|
|
1356
|
+
(width / pixelRatio) +
|
|
1357
|
+
", height=" +
|
|
1358
|
+
(height / pixelRatio)
|
|
1359
|
+
);
|
|
1360
|
+
Log.d("CameraPreview", "12. PIXEL RATIO - " + pixelRatio);
|
|
1361
|
+
Log.d("CameraPreview", "========================");
|
|
1362
|
+
|
|
1363
|
+
// Calculate logical values with proper rounding to avoid sub-pixel issues
|
|
1364
|
+
double logicalWidth = width / pixelRatio;
|
|
1365
|
+
double logicalHeight = height / pixelRatio;
|
|
1366
|
+
double logicalX = x / pixelRatio;
|
|
1367
|
+
double logicalY = relativeY / pixelRatio;
|
|
1368
|
+
|
|
1369
|
+
JSObject result = getViewSize(
|
|
1370
|
+
logicalX,
|
|
1371
|
+
logicalY,
|
|
1372
|
+
logicalWidth,
|
|
1373
|
+
logicalHeight
|
|
1374
|
+
);
|
|
1375
|
+
|
|
1376
|
+
// Log exact calculations to debug one-pixel difference
|
|
1377
|
+
Log.d("CameraPreview", "========================");
|
|
1378
|
+
Log.d("CameraPreview", "FINAL POSITION CALCULATIONS:");
|
|
1379
|
+
Log.d(
|
|
1380
|
+
"CameraPreview",
|
|
1381
|
+
"Pixel values: x=" +
|
|
1382
|
+
x +
|
|
1383
|
+
", y=" +
|
|
1384
|
+
relativeY +
|
|
1385
|
+
", width=" +
|
|
1386
|
+
width +
|
|
1387
|
+
", height=" +
|
|
1388
|
+
height
|
|
1389
|
+
);
|
|
1390
|
+
Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
|
|
1391
|
+
Log.d(
|
|
1392
|
+
"CameraPreview",
|
|
1393
|
+
"Logical values (exact): x=" +
|
|
1394
|
+
logicalX +
|
|
1395
|
+
", y=" +
|
|
1396
|
+
logicalY +
|
|
1397
|
+
", width=" +
|
|
1398
|
+
logicalWidth +
|
|
1399
|
+
", height=" +
|
|
1400
|
+
logicalHeight
|
|
1401
|
+
);
|
|
1402
|
+
Log.d(
|
|
1403
|
+
"CameraPreview",
|
|
1404
|
+
"Logical values (rounded): x=" +
|
|
1405
|
+
Math.round(logicalX) +
|
|
1406
|
+
", y=" +
|
|
1407
|
+
Math.round(logicalY) +
|
|
1408
|
+
", width=" +
|
|
1409
|
+
Math.round(logicalWidth) +
|
|
1410
|
+
", height=" +
|
|
1411
|
+
Math.round(logicalHeight)
|
|
1412
|
+
);
|
|
1413
|
+
|
|
1414
|
+
// Check if previewContainer has any padding or margin that might cause offset
|
|
1415
|
+
if (cameraXView != null) {
|
|
1416
|
+
View previewContainer = cameraXView.getPreviewContainer();
|
|
1417
|
+
if (previewContainer != null) {
|
|
1418
|
+
Log.d(
|
|
1419
|
+
"CameraPreview",
|
|
1420
|
+
"PreviewContainer padding: left=" +
|
|
1421
|
+
previewContainer.getPaddingLeft() +
|
|
1422
|
+
", top=" +
|
|
1423
|
+
previewContainer.getPaddingTop() +
|
|
1424
|
+
", right=" +
|
|
1425
|
+
previewContainer.getPaddingRight() +
|
|
1426
|
+
", bottom=" +
|
|
1427
|
+
previewContainer.getPaddingBottom()
|
|
1428
|
+
);
|
|
1429
|
+
ViewGroup.LayoutParams params = previewContainer.getLayoutParams();
|
|
1430
|
+
if (params instanceof ViewGroup.MarginLayoutParams) {
|
|
1431
|
+
ViewGroup.MarginLayoutParams marginParams =
|
|
1432
|
+
(ViewGroup.MarginLayoutParams) params;
|
|
1433
|
+
Log.d(
|
|
1434
|
+
"CameraPreview",
|
|
1435
|
+
"PreviewContainer margins: left=" +
|
|
1436
|
+
marginParams.leftMargin +
|
|
1437
|
+
", top=" +
|
|
1438
|
+
marginParams.topMargin +
|
|
1439
|
+
", right=" +
|
|
1440
|
+
marginParams.rightMargin +
|
|
1441
|
+
", bottom=" +
|
|
1442
|
+
marginParams.bottomMargin
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
Log.d("CameraPreview", "========================");
|
|
1448
|
+
|
|
1449
|
+
// Log what we're returning
|
|
1450
|
+
Log.d(
|
|
1451
|
+
"CameraPreview",
|
|
1452
|
+
"Returning to JS - x: " +
|
|
1453
|
+
logicalX +
|
|
1454
|
+
" (from " +
|
|
1455
|
+
logicalX +
|
|
1456
|
+
"), y: " +
|
|
1457
|
+
logicalY +
|
|
1458
|
+
" (from " +
|
|
1459
|
+
logicalY +
|
|
1460
|
+
"), width: " +
|
|
1461
|
+
logicalWidth +
|
|
1462
|
+
" (from " +
|
|
1463
|
+
logicalWidth +
|
|
1464
|
+
"), height: " +
|
|
1465
|
+
logicalHeight +
|
|
1466
|
+
" (from " +
|
|
1467
|
+
logicalHeight +
|
|
1468
|
+
")"
|
|
1469
|
+
);
|
|
1470
|
+
|
|
1471
|
+
call.resolve(result);
|
|
1472
|
+
bridge.releaseCall(call);
|
|
1473
|
+
cameraStartCallbackId = null; // Prevent re-use
|
|
526
1474
|
}
|
|
527
1475
|
}
|
|
528
1476
|
|
|
@@ -542,9 +1490,9 @@ public class CameraPreview
|
|
|
542
1490
|
public void onCameraStartError(String message) {
|
|
543
1491
|
PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
|
|
544
1492
|
if (call != null) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1493
|
+
call.reject(message);
|
|
1494
|
+
bridge.releaseCall(call);
|
|
1495
|
+
cameraStartCallbackId = null;
|
|
548
1496
|
}
|
|
549
1497
|
}
|
|
550
1498
|
|
|
@@ -557,19 +1505,20 @@ public class CameraPreview
|
|
|
557
1505
|
String aspectRatio = call.getString("aspectRatio", "4:3");
|
|
558
1506
|
Float x = call.getFloat("x");
|
|
559
1507
|
Float y = call.getFloat("y");
|
|
560
|
-
|
|
561
|
-
getActivity()
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
1508
|
+
|
|
1509
|
+
getActivity()
|
|
1510
|
+
.runOnUiThread(() -> {
|
|
1511
|
+
cameraXView.setAspectRatio(aspectRatio, x, y, () -> {
|
|
1512
|
+
// Return the actual preview bounds after layout and camera operations are complete
|
|
1513
|
+
int[] bounds = cameraXView.getCurrentPreviewBounds();
|
|
1514
|
+
JSObject ret = new JSObject();
|
|
1515
|
+
ret.put("x", bounds[0]);
|
|
1516
|
+
ret.put("y", bounds[1]);
|
|
1517
|
+
ret.put("width", bounds[2]);
|
|
1518
|
+
ret.put("height", bounds[3]);
|
|
1519
|
+
call.resolve(ret);
|
|
1520
|
+
});
|
|
571
1521
|
});
|
|
572
|
-
});
|
|
573
1522
|
}
|
|
574
1523
|
|
|
575
1524
|
@PluginMethod
|
|
@@ -591,10 +1540,11 @@ public class CameraPreview
|
|
|
591
1540
|
return;
|
|
592
1541
|
}
|
|
593
1542
|
String gridMode = call.getString("gridMode", "none");
|
|
594
|
-
getActivity()
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1543
|
+
getActivity()
|
|
1544
|
+
.runOnUiThread(() -> {
|
|
1545
|
+
cameraXView.setGridMode(gridMode);
|
|
1546
|
+
call.resolve();
|
|
1547
|
+
});
|
|
598
1548
|
}
|
|
599
1549
|
|
|
600
1550
|
@PluginMethod
|
|
@@ -614,40 +1564,84 @@ public class CameraPreview
|
|
|
614
1564
|
call.reject("Camera is not running");
|
|
615
1565
|
return;
|
|
616
1566
|
}
|
|
617
|
-
|
|
1567
|
+
|
|
618
1568
|
// Convert pixel values back to logical units
|
|
619
|
-
DisplayMetrics metrics = getBridge()
|
|
1569
|
+
DisplayMetrics metrics = getBridge()
|
|
1570
|
+
.getActivity()
|
|
1571
|
+
.getResources()
|
|
1572
|
+
.getDisplayMetrics();
|
|
620
1573
|
float pixelRatio = metrics.density;
|
|
621
|
-
|
|
1574
|
+
|
|
622
1575
|
JSObject ret = new JSObject();
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1576
|
+
// Use same rounding strategy as start method
|
|
1577
|
+
double x = Math.ceil(cameraXView.getPreviewX() / pixelRatio);
|
|
1578
|
+
double y = Math.ceil(cameraXView.getPreviewY() / pixelRatio);
|
|
1579
|
+
double width = Math.floor(cameraXView.getPreviewWidth() / pixelRatio);
|
|
1580
|
+
double height = Math.floor(cameraXView.getPreviewHeight() / pixelRatio);
|
|
1581
|
+
|
|
1582
|
+
Log.d(
|
|
1583
|
+
"CameraPreview",
|
|
1584
|
+
"getPreviewSize: x=" +
|
|
1585
|
+
x +
|
|
1586
|
+
", y=" +
|
|
1587
|
+
y +
|
|
1588
|
+
", width=" +
|
|
1589
|
+
width +
|
|
1590
|
+
", height=" +
|
|
1591
|
+
height
|
|
1592
|
+
);
|
|
1593
|
+
ret.put("x", x);
|
|
1594
|
+
ret.put("y", y);
|
|
1595
|
+
ret.put("width", width);
|
|
1596
|
+
ret.put("height", height);
|
|
627
1597
|
call.resolve(ret);
|
|
628
1598
|
}
|
|
1599
|
+
|
|
629
1600
|
@PluginMethod
|
|
630
1601
|
public void setPreviewSize(PluginCall call) {
|
|
631
1602
|
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
632
1603
|
call.reject("Camera is not running");
|
|
633
1604
|
return;
|
|
634
1605
|
}
|
|
635
|
-
|
|
1606
|
+
|
|
636
1607
|
// Get values from call - null values will become 0
|
|
637
1608
|
Integer xParam = call.getInt("x");
|
|
638
1609
|
Integer yParam = call.getInt("y");
|
|
639
1610
|
Integer widthParam = call.getInt("width");
|
|
640
1611
|
Integer heightParam = call.getInt("height");
|
|
641
|
-
|
|
1612
|
+
|
|
642
1613
|
// Apply pixel ratio conversion to non-null values
|
|
643
|
-
DisplayMetrics metrics = getBridge()
|
|
1614
|
+
DisplayMetrics metrics = getBridge()
|
|
1615
|
+
.getActivity()
|
|
1616
|
+
.getResources()
|
|
1617
|
+
.getDisplayMetrics();
|
|
644
1618
|
float pixelRatio = metrics.density;
|
|
645
|
-
|
|
1619
|
+
|
|
1620
|
+
// Check if edge-to-edge mode is active
|
|
1621
|
+
WebView webView = getBridge().getWebView();
|
|
1622
|
+
int webViewTopInset = 0;
|
|
1623
|
+
boolean isEdgeToEdgeActive = false;
|
|
1624
|
+
if (webView != null) {
|
|
1625
|
+
int[] location = new int[2];
|
|
1626
|
+
webView.getLocationOnScreen(location);
|
|
1627
|
+
webViewTopInset = location[1];
|
|
1628
|
+
isEdgeToEdgeActive = webViewTopInset > 0;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
646
1631
|
int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
|
|
647
1632
|
int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1633
|
+
|
|
1634
|
+
// Add edge-to-edge inset to Y if active
|
|
1635
|
+
if (isEdgeToEdgeActive && y > 0) {
|
|
1636
|
+
y += webViewTopInset;
|
|
1637
|
+
}
|
|
1638
|
+
int width = (widthParam != null && widthParam > 0)
|
|
1639
|
+
? (int) (widthParam * pixelRatio)
|
|
1640
|
+
: 0;
|
|
1641
|
+
int height = (heightParam != null && heightParam > 0)
|
|
1642
|
+
? (int) (heightParam * pixelRatio)
|
|
1643
|
+
: 0;
|
|
1644
|
+
|
|
651
1645
|
cameraXView.setPreviewSize(x, y, width, height, () -> {
|
|
652
1646
|
// Return the actual preview bounds after layout operations are complete
|
|
653
1647
|
int[] bounds = cameraXView.getCurrentPreviewBounds();
|
|
@@ -659,4 +1653,116 @@ public class CameraPreview
|
|
|
659
1653
|
call.resolve(ret);
|
|
660
1654
|
});
|
|
661
1655
|
}
|
|
1656
|
+
|
|
1657
|
+
@PluginMethod
|
|
1658
|
+
public void deleteFile(PluginCall call) {
|
|
1659
|
+
String path = call.getString("path");
|
|
1660
|
+
if (path == null || path.isEmpty()) {
|
|
1661
|
+
call.reject("path parameter is required");
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
java.io.File f = new java.io.File(android.net.Uri.parse(path).getPath());
|
|
1666
|
+
boolean deleted = f.exists() && f.delete();
|
|
1667
|
+
JSObject ret = new JSObject();
|
|
1668
|
+
ret.put("success", deleted);
|
|
1669
|
+
call.resolve(ret);
|
|
1670
|
+
} catch (Exception e) {
|
|
1671
|
+
call.reject("Failed to delete file: " + e.getMessage());
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
@PluginMethod
|
|
1676
|
+
public void getSafeAreaInsets(PluginCall call) {
|
|
1677
|
+
JSObject ret = new JSObject();
|
|
1678
|
+
int orientation = getContext()
|
|
1679
|
+
.getResources()
|
|
1680
|
+
.getConfiguration()
|
|
1681
|
+
.orientation;
|
|
1682
|
+
|
|
1683
|
+
int notchInsetPx = 0;
|
|
1684
|
+
|
|
1685
|
+
try {
|
|
1686
|
+
View decorView = getBridge().getActivity().getWindow().getDecorView();
|
|
1687
|
+
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
|
|
1688
|
+
|
|
1689
|
+
if (insets != null) {
|
|
1690
|
+
// Get display cutout insets (notch, punch hole, etc.)
|
|
1691
|
+
// this.Capacitor.Plugins.CameraPreview.getSafeAreaInsets()
|
|
1692
|
+
Insets cutout = insets.getInsets(
|
|
1693
|
+
WindowInsetsCompat.Type.displayCutout()
|
|
1694
|
+
);
|
|
1695
|
+
|
|
1696
|
+
// Get system bars insets (status bar, navigation bars)
|
|
1697
|
+
Insets sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
|
1698
|
+
|
|
1699
|
+
// In portrait mode, notch is at the top
|
|
1700
|
+
// In landscape mode, notch is typically at the left side (or right, but left is more common)
|
|
1701
|
+
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
|
1702
|
+
// Portrait: return top inset (notch/status bar)
|
|
1703
|
+
notchInsetPx = Math.max(cutout.top, sysBars.top);
|
|
1704
|
+
|
|
1705
|
+
// If no cutout detected but we have system bars, use status bar height as fallback
|
|
1706
|
+
if (cutout.top == 0 && sysBars.top > 0) {
|
|
1707
|
+
notchInsetPx = sysBars.top;
|
|
1708
|
+
}
|
|
1709
|
+
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
1710
|
+
// Landscape: return left inset (notch moved to side)
|
|
1711
|
+
notchInsetPx = Math.max(cutout.left, sysBars.left);
|
|
1712
|
+
|
|
1713
|
+
// If no cutout detected but we have system bars, use left system bar as fallback
|
|
1714
|
+
if (cutout.left == 0 && sysBars.left > 0) {
|
|
1715
|
+
notchInsetPx = sysBars.left;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Additional fallback: some devices might have the notch on the right in landscape
|
|
1719
|
+
// If left is 0, check right side as well
|
|
1720
|
+
if (notchInsetPx == 0) {
|
|
1721
|
+
notchInsetPx = Math.max(cutout.right, sysBars.right);
|
|
1722
|
+
}
|
|
1723
|
+
} else {
|
|
1724
|
+
// Unknown orientation, default to top
|
|
1725
|
+
notchInsetPx = Math.max(cutout.top, sysBars.top);
|
|
1726
|
+
}
|
|
1727
|
+
} else {
|
|
1728
|
+
// Fallback to status bar height if WindowInsets are not available
|
|
1729
|
+
notchInsetPx = getStatusBarHeightPx();
|
|
1730
|
+
}
|
|
1731
|
+
} catch (Exception e) {
|
|
1732
|
+
// Final fallback
|
|
1733
|
+
notchInsetPx = getStatusBarHeightPx();
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// Convert pixels to dp for consistency with JS layout units
|
|
1737
|
+
float density = getContext().getResources().getDisplayMetrics().density;
|
|
1738
|
+
ret.put("orientation", orientation);
|
|
1739
|
+
ret.put("top", notchInsetPx / density);
|
|
1740
|
+
call.resolve(ret);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
private boolean approxEqualPx(int a, int b) {
|
|
1744
|
+
return Math.abs(a - b) <= 2; // within 2px tolerance
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
private int getStatusBarHeightPx() {
|
|
1748
|
+
int result = 0;
|
|
1749
|
+
int resourceId = getContext()
|
|
1750
|
+
.getResources()
|
|
1751
|
+
.getIdentifier("status_bar_height", "dimen", "android");
|
|
1752
|
+
if (resourceId > 0) {
|
|
1753
|
+
result = getContext().getResources().getDimensionPixelSize(resourceId);
|
|
1754
|
+
}
|
|
1755
|
+
return result;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
private int getNavigationBarHeightPx() {
|
|
1759
|
+
int result = 0;
|
|
1760
|
+
int resourceId = getContext()
|
|
1761
|
+
.getResources()
|
|
1762
|
+
.getIdentifier("navigation_bar_height", "dimen", "android");
|
|
1763
|
+
if (resourceId > 0) {
|
|
1764
|
+
result = getContext().getResources().getDimensionPixelSize(resourceId);
|
|
1765
|
+
}
|
|
1766
|
+
return result;
|
|
1767
|
+
}
|
|
662
1768
|
}
|