@capgo/camera-preview 7.4.0-alpha.3 → 7.4.0-alpha.35
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 +179 -12
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +585 -11
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +994 -423
- package/dist/docs.json +312 -11
- package/dist/esm/definitions.d.ts +115 -11
- 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 +16 -1
- package/dist/esm/web.js +82 -5
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +105 -5
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +105 -5
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +143 -34
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +486 -132
- package/package.json +10 -2
|
@@ -5,13 +5,19 @@ 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;
|
|
8
10
|
import android.location.Location;
|
|
9
11
|
import android.util.DisplayMetrics;
|
|
10
12
|
import android.util.Log;
|
|
11
13
|
import android.util.Size;
|
|
14
|
+
import android.view.OrientationEventListener;
|
|
12
15
|
import android.view.View;
|
|
13
16
|
import android.view.ViewGroup;
|
|
14
17
|
import android.webkit.WebView;
|
|
18
|
+
import androidx.core.graphics.Insets;
|
|
19
|
+
import androidx.core.view.ViewCompat;
|
|
20
|
+
import androidx.core.view.WindowInsetsCompat;
|
|
15
21
|
import com.ahm.capacitor.camera.preview.model.CameraDevice;
|
|
16
22
|
import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
|
|
17
23
|
import com.ahm.capacitor.camera.preview.model.LensInfo;
|
|
@@ -56,6 +62,8 @@ public class CameraPreview
|
|
|
56
62
|
extends Plugin
|
|
57
63
|
implements CameraXView.CameraXViewListener {
|
|
58
64
|
|
|
65
|
+
private static final String TAG = "CameraPreview CameraXView";
|
|
66
|
+
|
|
59
67
|
static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
|
|
60
68
|
static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
|
|
61
69
|
static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS =
|
|
@@ -67,8 +75,31 @@ public class CameraPreview
|
|
|
67
75
|
private int previousOrientationRequest =
|
|
68
76
|
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
|
|
69
77
|
private CameraXView cameraXView;
|
|
78
|
+
private View rotationOverlay;
|
|
70
79
|
private FusedLocationProviderClient fusedLocationClient;
|
|
71
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
|
+
}
|
|
72
103
|
|
|
73
104
|
@PluginMethod
|
|
74
105
|
public void start(PluginCall call) {
|
|
@@ -205,6 +236,19 @@ public class CameraPreview
|
|
|
205
236
|
.getActivity()
|
|
206
237
|
.setRequestedOrientation(previousOrientationRequest);
|
|
207
238
|
|
|
239
|
+
// Disable and clear orientation listener
|
|
240
|
+
if (orientationListener != null) {
|
|
241
|
+
orientationListener.disable();
|
|
242
|
+
orientationListener = null;
|
|
243
|
+
lastOrientation = Configuration.ORIENTATION_UNDEFINED;
|
|
244
|
+
}
|
|
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
|
+
|
|
208
252
|
if (cameraXView != null && cameraXView.isRunning()) {
|
|
209
253
|
cameraXView.stopSession();
|
|
210
254
|
cameraXView = null;
|
|
@@ -215,6 +259,10 @@ public class CameraPreview
|
|
|
215
259
|
|
|
216
260
|
@PluginMethod
|
|
217
261
|
public void getSupportedFlashModes(PluginCall call) {
|
|
262
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
263
|
+
call.reject("Camera is not running");
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
218
266
|
List<String> supportedFlashModes = cameraXView.getSupportedFlashModes();
|
|
219
267
|
JSArray jsonFlashModes = new JSArray();
|
|
220
268
|
for (String mode : supportedFlashModes) {
|
|
@@ -268,6 +316,10 @@ public class CameraPreview
|
|
|
268
316
|
|
|
269
317
|
@PluginMethod
|
|
270
318
|
public void getZoom(PluginCall call) {
|
|
319
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
320
|
+
call.reject("Camera is not running");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
271
323
|
ZoomFactors zoomFactors = cameraXView.getZoomFactors();
|
|
272
324
|
JSObject result = new JSObject();
|
|
273
325
|
result.put("min", zoomFactors.getMin());
|
|
@@ -276,6 +328,57 @@ public class CameraPreview
|
|
|
276
328
|
call.resolve(result);
|
|
277
329
|
}
|
|
278
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
|
+
|
|
279
382
|
@PluginMethod
|
|
280
383
|
public void setZoom(PluginCall call) {
|
|
281
384
|
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
@@ -287,9 +390,8 @@ public class CameraPreview
|
|
|
287
390
|
call.reject("level parameter is required");
|
|
288
391
|
return;
|
|
289
392
|
}
|
|
290
|
-
Boolean autoFocus = call.getBoolean("autoFocus", true);
|
|
291
393
|
try {
|
|
292
|
-
cameraXView.setZoom(level
|
|
394
|
+
cameraXView.setZoom(level);
|
|
293
395
|
call.resolve();
|
|
294
396
|
} catch (Exception e) {
|
|
295
397
|
call.reject("Failed to set zoom: " + e.getMessage());
|
|
@@ -922,6 +1024,229 @@ public class CameraPreview
|
|
|
922
1024
|
bridge.saveCall(call);
|
|
923
1025
|
cameraStartCallbackId = call.getCallbackId();
|
|
924
1026
|
cameraXView.startSession(config);
|
|
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]
|
|
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
|
+
});
|
|
925
1250
|
});
|
|
926
1251
|
}
|
|
927
1252
|
|
|
@@ -950,6 +1275,23 @@ public class CameraPreview
|
|
|
950
1275
|
bridge.releaseCall(pluginCall);
|
|
951
1276
|
}
|
|
952
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
|
+
|
|
953
1295
|
@Override
|
|
954
1296
|
public void onCameraStarted(int width, int height, int x, int y) {
|
|
955
1297
|
PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
|
|
@@ -1018,11 +1360,114 @@ public class CameraPreview
|
|
|
1018
1360
|
Log.d("CameraPreview", "12. PIXEL RATIO - " + pixelRatio);
|
|
1019
1361
|
Log.d("CameraPreview", "========================");
|
|
1020
1362
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
+
|
|
1026
1471
|
call.resolve(result);
|
|
1027
1472
|
bridge.releaseCall(call);
|
|
1028
1473
|
cameraStartCallbackId = null; // Prevent re-use
|
|
@@ -1128,10 +1573,27 @@ public class CameraPreview
|
|
|
1128
1573
|
float pixelRatio = metrics.density;
|
|
1129
1574
|
|
|
1130
1575
|
JSObject ret = new JSObject();
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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);
|
|
1135
1597
|
call.resolve(ret);
|
|
1136
1598
|
}
|
|
1137
1599
|
|
|
@@ -1191,4 +1653,116 @@ public class CameraPreview
|
|
|
1191
1653
|
call.resolve(ret);
|
|
1192
1654
|
});
|
|
1193
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
|
+
}
|
|
1194
1768
|
}
|