@capgo/camera-preview 7.4.0-beta.7 → 7.4.0-beta.9

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 (41) hide show
  1. package/README.md +63 -29
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/buildOutputCleanup/cache.properties +1 -1
  12. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  13. package/android/.gradle/file-system.probe +0 -0
  14. package/android/build.gradle +1 -0
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +127 -14
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +529 -29
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +2 -0
  18. package/dist/docs.json +46 -7
  19. package/dist/esm/definitions.d.ts +42 -5
  20. package/dist/esm/definitions.js.map +1 -1
  21. package/dist/esm/web.d.ts +25 -1
  22. package/dist/esm/web.js +81 -9
  23. package/dist/esm/web.js.map +1 -1
  24. package/dist/plugin.cjs.js +81 -9
  25. package/dist/plugin.cjs.js.map +1 -1
  26. package/dist/plugin.js +81 -9
  27. package/dist/plugin.js.map +1 -1
  28. package/ios/Sources/CapgoCameraPreview/CameraController.swift +95 -18
  29. package/ios/Sources/CapgoCameraPreview/Plugin.swift +449 -111
  30. package/package.json +1 -1
  31. package/android/.gradle/config.properties +0 -2
  32. package/android/.idea/AndroidProjectSystem.xml +0 -6
  33. package/android/.idea/caches/deviceStreaming.xml +0 -811
  34. package/android/.idea/compiler.xml +0 -6
  35. package/android/.idea/gradle.xml +0 -18
  36. package/android/.idea/migrations.xml +0 -10
  37. package/android/.idea/misc.xml +0 -10
  38. package/android/.idea/runConfigurations.xml +0 -17
  39. package/android/.idea/vcs.xml +0 -6
  40. package/android/.idea/workspace.xml +0 -55
  41. package/android/local.properties +0 -8
@@ -5,6 +5,7 @@ import android.hardware.camera2.CameraAccessException;
5
5
  import android.hardware.camera2.CameraManager;
6
6
  import android.os.Build;
7
7
  import android.util.Base64;
8
+ import android.util.DisplayMetrics;
8
9
  import android.util.Log;
9
10
  import android.util.Size;
10
11
  import android.view.ViewGroup;
@@ -68,6 +69,7 @@ import android.widget.FrameLayout;
68
69
  import androidx.lifecycle.LifecycleObserver;
69
70
  import androidx.lifecycle.OnLifecycleEvent;
70
71
  import android.util.Rational;
72
+ import android.view.ViewGroup;
71
73
 
72
74
  public class CameraXView implements LifecycleOwner, LifecycleObserver {
73
75
  private static final String TAG = "CameraPreview CameraXView";
@@ -202,19 +204,86 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
202
204
 
203
205
  // Create and setup the grid overlay
204
206
  gridOverlayView = new GridOverlayView(context);
205
- gridOverlayView.setGridMode(sessionConfig.getGridMode());
206
207
  previewContainer.addView(gridOverlayView, new FrameLayout.LayoutParams(
207
208
  FrameLayout.LayoutParams.MATCH_PARENT,
208
209
  FrameLayout.LayoutParams.MATCH_PARENT
209
210
  ));
211
+ // Set grid mode after adding to container to ensure proper layout
212
+ gridOverlayView.post(() -> {
213
+ String currentGridMode = sessionConfig.getGridMode();
214
+ Log.d(TAG, "setupPreviewView: Setting grid mode to: " + currentGridMode);
215
+ gridOverlayView.setGridMode(currentGridMode);
216
+ });
210
217
 
211
218
  ViewGroup parent = (ViewGroup) webView.getParent();
212
219
  if (parent != null) {
213
- parent.addView(previewContainer, new ViewGroup.LayoutParams(sessionConfig.getWidth(), sessionConfig.getHeight()));
220
+ FrameLayout.LayoutParams layoutParams = calculatePreviewLayoutParams();
221
+ parent.addView(previewContainer, layoutParams);
214
222
  if(sessionConfig.isToBack()) webView.bringToFront();
215
223
  }
216
224
  }
217
225
 
226
+ private FrameLayout.LayoutParams calculatePreviewLayoutParams() {
227
+ // sessionConfig already contains pixel-converted coordinates with webview offsets applied
228
+ int x = sessionConfig.getX();
229
+ int y = sessionConfig.getY();
230
+ int width = sessionConfig.getWidth();
231
+ int height = sessionConfig.getHeight();
232
+ String aspectRatio = sessionConfig.getAspectRatio();
233
+
234
+ Log.d(TAG, "calculatePreviewLayoutParams: Using sessionConfig values - x:" + x + " y:" + y + " width:" + width + " height:" + height + " aspectRatio:" + aspectRatio);
235
+
236
+ // Apply aspect ratio if specified and no explicit size was given
237
+ if (aspectRatio != null && !aspectRatio.isEmpty()) {
238
+ String[] ratios = aspectRatio.split(":");
239
+ if (ratios.length == 2) {
240
+ try {
241
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
242
+ float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
243
+
244
+ // Calculate optimal size while maintaining aspect ratio
245
+ int optimalWidth = width;
246
+ int optimalHeight = (int) (width / ratio);
247
+
248
+ if (optimalHeight > height) {
249
+ // Height constraint is tighter, fit by height
250
+ optimalHeight = height;
251
+ optimalWidth = (int) (height * ratio);
252
+ }
253
+
254
+ width = optimalWidth;
255
+ height = optimalHeight;
256
+ Log.d(TAG, "calculatePreviewLayoutParams: Applied aspect ratio " + aspectRatio + " - new size: " + width + "x" + height);
257
+ } catch (NumberFormatException e) {
258
+ Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
259
+ }
260
+ }
261
+ }
262
+
263
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
264
+
265
+ // Only add insets for positioning coordinates, not for full-screen sizes
266
+ int webViewTopInset = getWebViewTopInset();
267
+ int webViewLeftInset = getWebViewLeftInset();
268
+
269
+ // Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
270
+ if (x == 0 && y == 0) {
271
+ layoutParams.leftMargin = x;
272
+ layoutParams.topMargin = y;
273
+ Log.d(TAG, "calculatePreviewLayoutParams: Full-screen mode - keeping position (0,0) without insets");
274
+ } else {
275
+ layoutParams.leftMargin = x + webViewLeftInset;
276
+ layoutParams.topMargin = y + webViewTopInset;
277
+ Log.d(TAG, "calculatePreviewLayoutParams: Positioned mode - applying insets");
278
+ }
279
+
280
+ Log.d(TAG, "calculatePreviewLayoutParams: Applied insets - x:" + x + "+" + webViewLeftInset + "=" + layoutParams.leftMargin +
281
+ ", y:" + y + "+" + webViewTopInset + "=" + layoutParams.topMargin);
282
+
283
+ Log.d(TAG, "calculatePreviewLayoutParams: Final layout - x:" + x + " y:" + y + " width:" + width + " height:" + height);
284
+ return layoutParams;
285
+ }
286
+
218
287
  private void removePreviewView() {
219
288
  if (previewContainer != null) {
220
289
  ViewGroup parent = (ViewGroup) previewContainer.getParent();
@@ -305,12 +374,17 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
305
374
 
306
375
  isRunning = true;
307
376
  Log.d(TAG, "bindCameraUseCases: Camera bound successfully");
308
- if (listener != null) listener.onCameraStarted(
309
- sessionConfig.getWidth(),
310
- sessionConfig.getHeight(),
311
- sessionConfig.getX(),
312
- sessionConfig.getY()
313
- );
377
+ if (listener != null) {
378
+ // Post the callback to ensure layout is complete
379
+ previewContainer.post(() -> {
380
+ // Return actual preview container dimensions instead of requested dimensions
381
+ int actualWidth = previewContainer != null ? previewContainer.getWidth() : sessionConfig.getWidth();
382
+ int actualHeight = previewContainer != null ? previewContainer.getHeight() : sessionConfig.getHeight();
383
+ int actualX = previewContainer != null ? previewContainer.getLeft() : sessionConfig.getX();
384
+ int actualY = previewContainer != null ? previewContainer.getTop() : sessionConfig.getY();
385
+ listener.onCameraStarted(actualWidth, actualHeight, actualX, actualY);
386
+ });
387
+ }
314
388
  } catch (Exception e) {
315
389
  if (listener != null) listener.onCameraStartError("Error binding camera: " + e.getMessage());
316
390
  }
@@ -418,6 +492,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
418
492
  ByteArrayOutputStream stream = new ByteArrayOutputStream();
419
493
  resizedBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream);
420
494
  bytes = stream.toByteArray();
495
+
496
+ // Write EXIF data back to resized image
497
+ bytes = writeExifToImageBytes(bytes, exifInterface);
498
+ } else {
499
+ // For non-resized images, ensure EXIF is saved
500
+ exifInterface.saveAttributes();
501
+ bytes = new byte[(int) tempFile.length()];
502
+ java.io.FileInputStream fis2 = new java.io.FileInputStream(tempFile);
503
+ fis2.read(bytes);
504
+ fis2.close();
421
505
  }
422
506
 
423
507
  if (saveToGallery) {
@@ -606,6 +690,46 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
606
690
  {ExifInterface.TAG_Y_RESOLUTION, "YResolution"}
607
691
  };
608
692
 
693
+ private byte[] writeExifToImageBytes(byte[] imageBytes, ExifInterface sourceExif) {
694
+ try {
695
+ // Create a temporary file to write the image with EXIF
696
+ File tempExifFile = File.createTempFile("temp_exif", ".jpg", context.getCacheDir());
697
+
698
+ // Write the image bytes to temp file
699
+ java.io.FileOutputStream fos = new java.io.FileOutputStream(tempExifFile);
700
+ fos.write(imageBytes);
701
+ fos.close();
702
+
703
+ // Create new ExifInterface for the temp file and copy all EXIF data
704
+ ExifInterface newExif = new ExifInterface(tempExifFile.getAbsolutePath());
705
+
706
+ // Copy all EXIF attributes from source to new
707
+ for (String[] tag : EXIF_TAGS) {
708
+ String value = sourceExif.getAttribute(tag[0]);
709
+ if (value != null) {
710
+ newExif.setAttribute(tag[0], value);
711
+ }
712
+ }
713
+
714
+ // Save the EXIF data
715
+ newExif.saveAttributes();
716
+
717
+ // Read the file back with EXIF embedded
718
+ byte[] result = new byte[(int) tempExifFile.length()];
719
+ java.io.FileInputStream fis = new java.io.FileInputStream(tempExifFile);
720
+ fis.read(result);
721
+ fis.close();
722
+
723
+ // Clean up temp file
724
+ tempExifFile.delete();
725
+
726
+ return result;
727
+ } catch (Exception e) {
728
+ Log.e(TAG, "writeExifToImageBytes: Error writing EXIF data", e);
729
+ return imageBytes; // Return original bytes if error
730
+ }
731
+ }
732
+
609
733
  public void captureSample(int quality) {
610
734
  Log.d(TAG, "captureSample: Starting sample capture with quality: " + quality);
611
735
 
@@ -1058,7 +1182,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1058
1182
 
1059
1183
  if (sessionConfig.getAspectRatio() != null) {
1060
1184
  String[] ratios = sessionConfig.getAspectRatio().split(":");
1061
- float ratio = Float.parseFloat(ratios[0]) / Float.parseFloat(ratios[1]);
1185
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
1186
+ float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
1062
1187
  if (sessionConfig.getWidth() > 0) {
1063
1188
  layoutParams.height = (int) (sessionConfig.getWidth() / ratio);
1064
1189
  } else if (sessionConfig.getHeight() > 0) {
@@ -1093,31 +1218,88 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1093
1218
  }
1094
1219
 
1095
1220
  public void setAspectRatio(String aspectRatio) {
1096
- if (sessionConfig != null) {
1097
- sessionConfig = new CameraSessionConfiguration(
1098
- sessionConfig.getDeviceId(),
1099
- sessionConfig.getPosition(),
1100
- sessionConfig.getX(),
1101
- sessionConfig.getY(),
1102
- sessionConfig.getWidth(),
1103
- sessionConfig.getHeight(),
1104
- sessionConfig.getPaddingBottom(),
1105
- sessionConfig.getToBack(),
1106
- sessionConfig.getStoreToFile(),
1107
- sessionConfig.getEnableOpacity(),
1108
- sessionConfig.getEnableZoom(),
1109
- sessionConfig.getDisableExifHeaderStripping(),
1110
- sessionConfig.getDisableAudio(),
1111
- sessionConfig.getZoomFactor(),
1112
- aspectRatio,
1113
- sessionConfig.getGridMode()
1114
- );
1115
- updateLayoutParams();
1221
+ setAspectRatio(aspectRatio, null, null);
1222
+ }
1223
+
1224
+ public void setAspectRatio(String aspectRatio, Float x, Float y) {
1225
+ setAspectRatio(aspectRatio, x, y, null);
1226
+ }
1227
+
1228
+ public void setAspectRatio(String aspectRatio, Float x, Float y, Runnable callback) {
1229
+ if (sessionConfig == null) {
1230
+ if (callback != null) callback.run();
1231
+ return;
1232
+ }
1233
+
1234
+ String currentAspectRatio = sessionConfig.getAspectRatio();
1235
+
1236
+ // Don't restart camera if aspect ratio hasn't changed and no position specified
1237
+ if (aspectRatio != null && aspectRatio.equals(currentAspectRatio) && x == null && y == null) {
1238
+ Log.d(TAG, "setAspectRatio: Aspect ratio " + aspectRatio + " is already set and no position specified, skipping");
1239
+ if (callback != null) callback.run();
1240
+ return;
1241
+ }
1242
+
1243
+ String currentGridMode = sessionConfig.getGridMode();
1244
+ Log.d(TAG, "setAspectRatio: Changing from " + currentAspectRatio + " to " + aspectRatio +
1245
+ (x != null && y != null ? " at position (" + x + ", " + y + ")" : " with auto-centering") +
1246
+ ", preserving grid mode: " + currentGridMode);
1247
+
1248
+ sessionConfig = new CameraSessionConfiguration(
1249
+ sessionConfig.getDeviceId(),
1250
+ sessionConfig.getPosition(),
1251
+ sessionConfig.getX(),
1252
+ sessionConfig.getY(),
1253
+ sessionConfig.getWidth(),
1254
+ sessionConfig.getHeight(),
1255
+ sessionConfig.getPaddingBottom(),
1256
+ sessionConfig.getToBack(),
1257
+ sessionConfig.getStoreToFile(),
1258
+ sessionConfig.getEnableOpacity(),
1259
+ sessionConfig.getEnableZoom(),
1260
+ sessionConfig.getDisableExifHeaderStripping(),
1261
+ sessionConfig.getDisableAudio(),
1262
+ sessionConfig.getZoomFactor(),
1263
+ aspectRatio,
1264
+ currentGridMode
1265
+ );
1266
+
1267
+ // Update layout and rebind camera with new aspect ratio
1268
+ if (isRunning && previewContainer != null) {
1269
+ mainExecutor.execute(() -> {
1270
+ // First update the UI layout
1271
+ updatePreviewLayoutForAspectRatio(aspectRatio, x, y);
1272
+
1273
+ // Then rebind the camera with new aspect ratio configuration
1274
+ Log.d(TAG, "setAspectRatio: Rebinding camera with new aspect ratio: " + aspectRatio);
1275
+ bindCameraUseCases();
1276
+
1277
+ // Preserve grid mode and wait for completion
1278
+ if (gridOverlayView != null) {
1279
+ gridOverlayView.post(() -> {
1280
+ Log.d(TAG, "setAspectRatio: Re-applying grid mode: " + currentGridMode);
1281
+ gridOverlayView.setGridMode(currentGridMode);
1282
+
1283
+ // Wait one more frame for grid to be applied, then call callback
1284
+ if (callback != null) {
1285
+ gridOverlayView.post(callback);
1286
+ }
1287
+ });
1288
+ } else {
1289
+ // No grid overlay, wait one frame for layout completion then call callback
1290
+ if (callback != null) {
1291
+ previewContainer.post(callback);
1292
+ }
1293
+ }
1294
+ });
1295
+ } else {
1296
+ if (callback != null) callback.run();
1116
1297
  }
1117
1298
  }
1118
1299
 
1119
1300
  public void setGridMode(String gridMode) {
1120
1301
  if (sessionConfig != null) {
1302
+ Log.d(TAG, "setGridMode: Changing grid mode to: " + gridMode);
1121
1303
  sessionConfig = new CameraSessionConfiguration(
1122
1304
  sessionConfig.getDeviceId(),
1123
1305
  sessionConfig.getPosition(),
@@ -1140,9 +1322,327 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1140
1322
  // Update the grid overlay immediately
1141
1323
  if (gridOverlayView != null) {
1142
1324
  gridOverlayView.post(() -> {
1325
+ Log.d(TAG, "setGridMode: Applying grid mode to overlay: " + gridMode);
1143
1326
  gridOverlayView.setGridMode(gridMode);
1144
1327
  });
1145
1328
  }
1146
1329
  }
1147
1330
  }
1331
+
1332
+ public int getPreviewX() {
1333
+ if (previewContainer == null) return 0;
1334
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1335
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1336
+ // Return position relative to WebView content (subtract insets)
1337
+ int margin = ((ViewGroup.MarginLayoutParams) layoutParams).leftMargin;
1338
+ int leftInset = getWebViewLeftInset();
1339
+ int result = margin - leftInset;
1340
+ Log.d(TAG, "getPreviewX: leftMargin=" + margin + ", leftInset=" + leftInset + ", result=" + result);
1341
+ return result;
1342
+ }
1343
+ return previewContainer.getLeft();
1344
+ }
1345
+ public int getPreviewY() {
1346
+ if (previewContainer == null) return 0;
1347
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1348
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1349
+ // Return position relative to WebView content (subtract insets)
1350
+ int margin = ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
1351
+ int topInset = getWebViewTopInset();
1352
+ int result = margin - topInset;
1353
+ Log.d(TAG, "getPreviewY: topMargin=" + margin + ", topInset=" + topInset + ", result=" + result);
1354
+ return result;
1355
+ }
1356
+ return previewContainer.getTop();
1357
+ }
1358
+ public int getPreviewWidth() {
1359
+ return previewContainer != null ? previewContainer.getWidth() : 0;
1360
+ }
1361
+ public int getPreviewHeight() {
1362
+ return previewContainer != null ? previewContainer.getHeight() : 0;
1363
+ }
1364
+ public void setPreviewSize(int x, int y, int width, int height) {
1365
+ setPreviewSize(x, y, width, height, null);
1366
+ }
1367
+
1368
+ public void setPreviewSize(int x, int y, int width, int height, Runnable callback) {
1369
+ if (previewContainer == null) {
1370
+ if (callback != null) callback.run();
1371
+ return;
1372
+ }
1373
+
1374
+ // Ensure this runs on the main UI thread
1375
+ mainExecutor.execute(() -> {
1376
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1377
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1378
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layoutParams;
1379
+
1380
+ // Only add insets for positioning coordinates, not for full-screen sizes
1381
+ int webViewTopInset = getWebViewTopInset();
1382
+ int webViewLeftInset = getWebViewLeftInset();
1383
+
1384
+ // Handle positioning - preserve current values if new values are not specified (negative)
1385
+ if (x >= 0) {
1386
+ // Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
1387
+ if (x == 0 && y == 0) {
1388
+ params.leftMargin = x;
1389
+ Log.d(TAG, "setPreviewSize: Full-screen mode - keeping x=0 without insets");
1390
+ } else {
1391
+ params.leftMargin = x + webViewLeftInset;
1392
+ Log.d(TAG, "setPreviewSize: Positioned mode - x=" + x + " + inset=" + webViewLeftInset + " = " + (x + webViewLeftInset));
1393
+ }
1394
+ }
1395
+ if (y >= 0) {
1396
+ // Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
1397
+ if (x == 0 && y == 0) {
1398
+ params.topMargin = y;
1399
+ Log.d(TAG, "setPreviewSize: Full-screen mode - keeping y=0 without insets");
1400
+ } else {
1401
+ params.topMargin = y + webViewTopInset;
1402
+ Log.d(TAG, "setPreviewSize: Positioned mode - y=" + y + " + inset=" + webViewTopInset + " = " + (y + webViewTopInset));
1403
+ }
1404
+ }
1405
+ if (width > 0) params.width = width;
1406
+ if (height > 0) params.height = height;
1407
+
1408
+ previewContainer.setLayoutParams(params);
1409
+ previewContainer.requestLayout();
1410
+
1411
+ Log.d(TAG, "setPreviewSize: Updated to " + params.width + "x" + params.height + " at (" + params.leftMargin + "," + params.topMargin + ")");
1412
+
1413
+ // Update session config to reflect actual layout
1414
+ if (sessionConfig != null) {
1415
+ String currentAspectRatio = sessionConfig.getAspectRatio();
1416
+
1417
+ // Calculate aspect ratio from actual dimensions if both width and height are provided
1418
+ String calculatedAspectRatio = currentAspectRatio;
1419
+ if (params.width > 0 && params.height > 0) {
1420
+ // Always use larger dimension / smaller dimension for consistent comparison
1421
+ float ratio = Math.max(params.width, params.height) / (float) Math.min(params.width, params.height);
1422
+ // Standard ratios: 16:9 ≈ 1.778, 4:3 ≈ 1.333
1423
+ float ratio16_9 = 16f / 9f; // 1.778
1424
+ float ratio4_3 = 4f / 3f; // 1.333
1425
+
1426
+ // Determine closest standard aspect ratio
1427
+ if (Math.abs(ratio - ratio16_9) < Math.abs(ratio - ratio4_3)) {
1428
+ calculatedAspectRatio = "16:9";
1429
+ } else {
1430
+ calculatedAspectRatio = "4:3";
1431
+ }
1432
+ Log.d(TAG, "setPreviewSize: Calculated aspect ratio from " + params.width + "x" + params.height + " = " + calculatedAspectRatio + " (normalized ratio=" + ratio + ")");
1433
+ }
1434
+
1435
+ sessionConfig = new CameraSessionConfiguration(
1436
+ sessionConfig.getDeviceId(),
1437
+ sessionConfig.getPosition(),
1438
+ params.leftMargin,
1439
+ params.topMargin,
1440
+ params.width,
1441
+ params.height,
1442
+ sessionConfig.getPaddingBottom(),
1443
+ sessionConfig.getToBack(),
1444
+ sessionConfig.getStoreToFile(),
1445
+ sessionConfig.getEnableOpacity(),
1446
+ sessionConfig.getEnableZoom(),
1447
+ sessionConfig.getDisableExifHeaderStripping(),
1448
+ sessionConfig.getDisableAudio(),
1449
+ sessionConfig.getZoomFactor(),
1450
+ calculatedAspectRatio,
1451
+ sessionConfig.getGridMode()
1452
+ );
1453
+
1454
+ // If aspect ratio changed due to size update, rebind camera
1455
+ if (isRunning && !Objects.equals(currentAspectRatio, calculatedAspectRatio)) {
1456
+ Log.d(TAG, "setPreviewSize: Aspect ratio changed from " + currentAspectRatio + " to " + calculatedAspectRatio + ", rebinding camera");
1457
+ bindCameraUseCases();
1458
+
1459
+ // Wait for camera rebinding to complete, then call callback
1460
+ if (callback != null) {
1461
+ previewContainer.post(() -> previewContainer.post(callback));
1462
+ }
1463
+ } else {
1464
+ // No camera rebinding needed, wait for layout to complete then call callback
1465
+ if (callback != null) {
1466
+ previewContainer.post(callback);
1467
+ }
1468
+ }
1469
+ } else {
1470
+ // No sessionConfig, just wait for layout then call callback
1471
+ if (callback != null) {
1472
+ previewContainer.post(callback);
1473
+ }
1474
+ }
1475
+ } else {
1476
+ Log.w(TAG, "setPreviewSize: Cannot set margins on layout params of type " + layoutParams.getClass().getSimpleName());
1477
+ // Fallback: just set width and height if specified
1478
+ if (width > 0) layoutParams.width = width;
1479
+ if (height > 0) layoutParams.height = height;
1480
+ previewContainer.setLayoutParams(layoutParams);
1481
+ previewContainer.requestLayout();
1482
+
1483
+ // Wait for layout then call callback
1484
+ if (callback != null) {
1485
+ previewContainer.post(callback);
1486
+ }
1487
+ }
1488
+ });
1489
+ }
1490
+
1491
+ private void updatePreviewLayoutForAspectRatio(String aspectRatio) {
1492
+ updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
1493
+ }
1494
+
1495
+ private void updatePreviewLayoutForAspectRatio(String aspectRatio, Float x, Float y) {
1496
+ if (previewContainer == null || aspectRatio == null) return;
1497
+
1498
+ // Parse aspect ratio
1499
+ String[] ratios = aspectRatio.split(":");
1500
+ if (ratios.length != 2) return;
1501
+
1502
+ try {
1503
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
1504
+ float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
1505
+
1506
+ // Get available space from webview dimensions
1507
+ int availableWidth = webView.getWidth();
1508
+ int availableHeight = webView.getHeight();
1509
+
1510
+ // Calculate position and size
1511
+ int finalX, finalY, finalWidth, finalHeight;
1512
+
1513
+ if (x != null && y != null) {
1514
+ // Account for WebView insets from edge-to-edge support
1515
+ int webViewTopInset = getWebViewTopInset();
1516
+ int webViewLeftInset = getWebViewLeftInset();
1517
+
1518
+ // Use provided coordinates with boundary checking, adjusted for insets
1519
+ finalX = Math.max(0, Math.min(x.intValue() + webViewLeftInset, availableWidth));
1520
+ finalY = Math.max(0, Math.min(y.intValue() + webViewTopInset, availableHeight));
1521
+
1522
+ // Calculate maximum available space from the given position
1523
+ int maxWidth = availableWidth - finalX;
1524
+ int maxHeight = availableHeight - finalY;
1525
+
1526
+ // Calculate optimal size while maintaining aspect ratio within available space
1527
+ finalWidth = maxWidth;
1528
+ finalHeight = (int) (maxWidth / ratio);
1529
+
1530
+ if (finalHeight > maxHeight) {
1531
+ // Height constraint is tighter, fit by height
1532
+ finalHeight = maxHeight;
1533
+ finalWidth = (int) (maxHeight * ratio);
1534
+ }
1535
+
1536
+ // Ensure final position stays within bounds
1537
+ finalX = Math.max(0, Math.min(finalX, availableWidth - finalWidth));
1538
+ finalY = Math.max(0, Math.min(finalY, availableHeight - finalHeight));
1539
+ } else {
1540
+ // Auto-center the view
1541
+ // Calculate size based on aspect ratio, using a reasonable base size
1542
+ // Use 80% of available space to ensure aspect ratio differences are visible
1543
+ int maxAvailableWidth = (int) (availableWidth * 0.8);
1544
+ int maxAvailableHeight = (int) (availableHeight * 0.8);
1545
+
1546
+ // Start with width-based calculation
1547
+ finalWidth = maxAvailableWidth;
1548
+ finalHeight = (int) (finalWidth / ratio);
1549
+
1550
+ // If height exceeds available space, use height-based calculation
1551
+ if (finalHeight > maxAvailableHeight) {
1552
+ finalHeight = maxAvailableHeight;
1553
+ finalWidth = (int) (finalHeight * ratio);
1554
+ }
1555
+
1556
+ // Center the view
1557
+ finalX = (availableWidth - finalWidth) / 2;
1558
+ finalY = (availableHeight - finalHeight) / 2;
1559
+
1560
+ Log.d(TAG, "updatePreviewLayoutForAspectRatio: Auto-center mode - ratio=" + ratio +
1561
+ ", calculated size=" + finalWidth + "x" + finalHeight +
1562
+ ", available=" + availableWidth + "x" + availableHeight);
1563
+ }
1564
+
1565
+ // Update layout params
1566
+ ViewGroup.LayoutParams currentParams = previewContainer.getLayoutParams();
1567
+ if (currentParams instanceof ViewGroup.MarginLayoutParams) {
1568
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) currentParams;
1569
+ params.width = finalWidth;
1570
+ params.height = finalHeight;
1571
+ params.leftMargin = finalX;
1572
+ params.topMargin = finalY;
1573
+ previewContainer.setLayoutParams(params);
1574
+ previewContainer.requestLayout();
1575
+ Log.d(TAG, "updatePreviewLayoutForAspectRatio: Updated to " + finalWidth + "x" + finalHeight + " at (" + finalX + "," + finalY + ")");
1576
+ }
1577
+ } catch (NumberFormatException e) {
1578
+ Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
1579
+ }
1580
+ }
1581
+
1582
+ private void updatePreviewLayout() {
1583
+ if (previewContainer == null || sessionConfig == null) return;
1584
+
1585
+ String aspectRatio = sessionConfig.getAspectRatio();
1586
+ if (aspectRatio == null) return;
1587
+
1588
+ updatePreviewLayoutForAspectRatio(aspectRatio);
1589
+ }
1590
+
1591
+ private int getWebViewTopInset() {
1592
+ try {
1593
+ if (webView != null) {
1594
+ ViewGroup.LayoutParams layoutParams = webView.getLayoutParams();
1595
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1596
+ return ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
1597
+ }
1598
+ }
1599
+ } catch (Exception e) {
1600
+ Log.w(TAG, "Failed to get WebView top inset", e);
1601
+ }
1602
+ return 0;
1603
+ }
1604
+
1605
+ private int getWebViewLeftInset() {
1606
+ try {
1607
+ if (webView != null) {
1608
+ ViewGroup.LayoutParams layoutParams = webView.getLayoutParams();
1609
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1610
+ return ((ViewGroup.MarginLayoutParams) layoutParams).leftMargin;
1611
+ }
1612
+ }
1613
+ } catch (Exception e) {
1614
+ Log.w(TAG, "Failed to get WebView left inset", e);
1615
+ }
1616
+ return 0;
1617
+ }
1618
+
1619
+ /**
1620
+ * Get the current preview position and size in DP units (without insets)
1621
+ */
1622
+ public int[] getCurrentPreviewBounds() {
1623
+ if (previewContainer == null) {
1624
+ return new int[]{0, 0, 0, 0}; // x, y, width, height
1625
+ }
1626
+
1627
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1628
+ int x = 0, y = 0, width = 0, height = 0;
1629
+
1630
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1631
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layoutParams;
1632
+
1633
+ // Remove insets to get original coordinates in DP
1634
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
1635
+ float pixelRatio = metrics.density;
1636
+
1637
+ int webViewTopInset = getWebViewTopInset();
1638
+ int webViewLeftInset = getWebViewLeftInset();
1639
+
1640
+ x = Math.max(0, (int) ((params.leftMargin - webViewLeftInset) / pixelRatio));
1641
+ y = Math.max(0, (int) ((params.topMargin - webViewTopInset) / pixelRatio));
1642
+ width = (int) (params.width / pixelRatio);
1643
+ height = (int) (params.height / pixelRatio);
1644
+ }
1645
+
1646
+ return new int[]{x, y, width, height};
1647
+ }
1148
1648
  }
@@ -34,8 +34,10 @@ public class GridOverlayView extends View {
34
34
  }
35
35
 
36
36
  public void setGridMode(String mode) {
37
+ String previousMode = this.gridMode;
37
38
  this.gridMode = mode != null ? mode : "none";
38
39
  setVisibility("none".equals(this.gridMode) ? View.GONE : View.VISIBLE);
40
+ android.util.Log.d("GridOverlayView", "setGridMode: Changed from '" + previousMode + "' to '" + this.gridMode + "', visibility: " + ("none".equals(this.gridMode) ? "GONE" : "VISIBLE"));
39
41
  invalidate();
40
42
  }
41
43