@capgo/camera-preview 7.4.0-beta.6 → 7.4.0-beta.8

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.
@@ -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";
@@ -77,7 +79,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
77
79
  void onPictureTakenError(String message);
78
80
  void onSampleTaken(String result);
79
81
  void onSampleTakenError(String message);
80
- void onCameraStarted();
82
+ void onCameraStarted(int width, int height, int x, int y);
81
83
  void onCameraStartError(String message);
82
84
  }
83
85
 
@@ -194,7 +196,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
194
196
 
195
197
  // Create and setup the preview view
196
198
  previewView = new PreviewView(context);
197
- previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
199
+ previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
198
200
  previewContainer.addView(previewView, new FrameLayout.LayoutParams(
199
201
  FrameLayout.LayoutParams.MATCH_PARENT,
200
202
  FrameLayout.LayoutParams.MATCH_PARENT
@@ -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,7 +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();
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
+ }
309
388
  } catch (Exception e) {
310
389
  if (listener != null) listener.onCameraStartError("Error binding camera: " + e.getMessage());
311
390
  }
@@ -398,7 +477,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
398
477
  java.io.FileInputStream fis = new java.io.FileInputStream(tempFile);
399
478
  fis.read(bytes);
400
479
  fis.close();
401
-
480
+
402
481
  ExifInterface exifInterface = new ExifInterface(tempFile.getAbsolutePath());
403
482
 
404
483
  if (location != null) {
@@ -1051,22 +1130,26 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1051
1130
  layoutParams.leftMargin = sessionConfig.getX();
1052
1131
  layoutParams.topMargin = sessionConfig.getY();
1053
1132
 
1054
- if (sessionConfig.getAspectRatio() != null && !sessionConfig.getAspectRatio().equals("fill")) {
1133
+ if (sessionConfig.getAspectRatio() != null) {
1055
1134
  String[] ratios = sessionConfig.getAspectRatio().split(":");
1056
- float ratio = Float.parseFloat(ratios[0]) / Float.parseFloat(ratios[1]);
1135
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
1136
+ float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
1057
1137
  if (sessionConfig.getWidth() > 0) {
1058
1138
  layoutParams.height = (int) (sessionConfig.getWidth() / ratio);
1059
1139
  } else if (sessionConfig.getHeight() > 0) {
1060
1140
  layoutParams.width = (int) (sessionConfig.getHeight() * ratio);
1061
1141
  }
1062
- } else {
1063
- previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
1064
1142
  }
1065
1143
 
1066
1144
  previewView.setLayoutParams(layoutParams);
1067
1145
 
1068
1146
  if (listener != null) {
1069
- listener.onCameraStarted();
1147
+ listener.onCameraStarted(
1148
+ sessionConfig.getWidth(),
1149
+ sessionConfig.getHeight(),
1150
+ sessionConfig.getX(),
1151
+ sessionConfig.getY()
1152
+ );
1070
1153
  }
1071
1154
  }
1072
1155
 
@@ -1074,11 +1157,99 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1074
1157
  if (sessionConfig != null) {
1075
1158
  return sessionConfig.getAspectRatio();
1076
1159
  }
1077
- return "fill";
1160
+ return "4:3";
1161
+ }
1162
+
1163
+ public String getGridMode() {
1164
+ if (sessionConfig != null) {
1165
+ return sessionConfig.getGridMode();
1166
+ }
1167
+ return "none";
1078
1168
  }
1079
1169
 
1080
1170
  public void setAspectRatio(String aspectRatio) {
1171
+ setAspectRatio(aspectRatio, null, null);
1172
+ }
1173
+
1174
+ public void setAspectRatio(String aspectRatio, Float x, Float y) {
1175
+ setAspectRatio(aspectRatio, x, y, null);
1176
+ }
1177
+
1178
+ public void setAspectRatio(String aspectRatio, Float x, Float y, Runnable callback) {
1179
+ if (sessionConfig == null) {
1180
+ if (callback != null) callback.run();
1181
+ return;
1182
+ }
1183
+
1184
+ String currentAspectRatio = sessionConfig.getAspectRatio();
1185
+
1186
+ // Don't restart camera if aspect ratio hasn't changed and no position specified
1187
+ if (aspectRatio != null && aspectRatio.equals(currentAspectRatio) && x == null && y == null) {
1188
+ Log.d(TAG, "setAspectRatio: Aspect ratio " + aspectRatio + " is already set and no position specified, skipping");
1189
+ if (callback != null) callback.run();
1190
+ return;
1191
+ }
1192
+
1193
+ String currentGridMode = sessionConfig.getGridMode();
1194
+ Log.d(TAG, "setAspectRatio: Changing from " + currentAspectRatio + " to " + aspectRatio +
1195
+ (x != null && y != null ? " at position (" + x + ", " + y + ")" : " with auto-centering") +
1196
+ ", preserving grid mode: " + currentGridMode);
1197
+
1198
+ sessionConfig = new CameraSessionConfiguration(
1199
+ sessionConfig.getDeviceId(),
1200
+ sessionConfig.getPosition(),
1201
+ sessionConfig.getX(),
1202
+ sessionConfig.getY(),
1203
+ sessionConfig.getWidth(),
1204
+ sessionConfig.getHeight(),
1205
+ sessionConfig.getPaddingBottom(),
1206
+ sessionConfig.getToBack(),
1207
+ sessionConfig.getStoreToFile(),
1208
+ sessionConfig.getEnableOpacity(),
1209
+ sessionConfig.getEnableZoom(),
1210
+ sessionConfig.getDisableExifHeaderStripping(),
1211
+ sessionConfig.getDisableAudio(),
1212
+ sessionConfig.getZoomFactor(),
1213
+ aspectRatio,
1214
+ currentGridMode
1215
+ );
1216
+
1217
+ // Update layout and rebind camera with new aspect ratio
1218
+ if (isRunning && previewContainer != null) {
1219
+ mainExecutor.execute(() -> {
1220
+ // First update the UI layout
1221
+ updatePreviewLayoutForAspectRatio(aspectRatio, x, y);
1222
+
1223
+ // Then rebind the camera with new aspect ratio configuration
1224
+ Log.d(TAG, "setAspectRatio: Rebinding camera with new aspect ratio: " + aspectRatio);
1225
+ bindCameraUseCases();
1226
+
1227
+ // Preserve grid mode and wait for completion
1228
+ if (gridOverlayView != null) {
1229
+ gridOverlayView.post(() -> {
1230
+ Log.d(TAG, "setAspectRatio: Re-applying grid mode: " + currentGridMode);
1231
+ gridOverlayView.setGridMode(currentGridMode);
1232
+
1233
+ // Wait one more frame for grid to be applied, then call callback
1234
+ if (callback != null) {
1235
+ gridOverlayView.post(callback);
1236
+ }
1237
+ });
1238
+ } else {
1239
+ // No grid overlay, wait one frame for layout completion then call callback
1240
+ if (callback != null) {
1241
+ previewContainer.post(callback);
1242
+ }
1243
+ }
1244
+ });
1245
+ } else {
1246
+ if (callback != null) callback.run();
1247
+ }
1248
+ }
1249
+
1250
+ public void setGridMode(String gridMode) {
1081
1251
  if (sessionConfig != null) {
1252
+ Log.d(TAG, "setGridMode: Changing grid mode to: " + gridMode);
1082
1253
  sessionConfig = new CameraSessionConfiguration(
1083
1254
  sessionConfig.getDeviceId(),
1084
1255
  sessionConfig.getPosition(),
@@ -1094,10 +1265,334 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1094
1265
  sessionConfig.getDisableExifHeaderStripping(),
1095
1266
  sessionConfig.getDisableAudio(),
1096
1267
  sessionConfig.getZoomFactor(),
1097
- aspectRatio,
1098
- sessionConfig.getGridMode()
1268
+ sessionConfig.getAspectRatio(),
1269
+ gridMode
1099
1270
  );
1100
- updateLayoutParams();
1271
+
1272
+ // Update the grid overlay immediately
1273
+ if (gridOverlayView != null) {
1274
+ gridOverlayView.post(() -> {
1275
+ Log.d(TAG, "setGridMode: Applying grid mode to overlay: " + gridMode);
1276
+ gridOverlayView.setGridMode(gridMode);
1277
+ });
1278
+ }
1279
+ }
1280
+ }
1281
+
1282
+ public int getPreviewX() {
1283
+ if (previewContainer == null) return 0;
1284
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1285
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1286
+ // Return position relative to WebView content (subtract insets)
1287
+ int margin = ((ViewGroup.MarginLayoutParams) layoutParams).leftMargin;
1288
+ int leftInset = getWebViewLeftInset();
1289
+ int result = margin - leftInset;
1290
+ Log.d(TAG, "getPreviewX: leftMargin=" + margin + ", leftInset=" + leftInset + ", result=" + result);
1291
+ return result;
1292
+ }
1293
+ return previewContainer.getLeft();
1294
+ }
1295
+ public int getPreviewY() {
1296
+ if (previewContainer == null) return 0;
1297
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1298
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1299
+ // Return position relative to WebView content (subtract insets)
1300
+ int margin = ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
1301
+ int topInset = getWebViewTopInset();
1302
+ int result = margin - topInset;
1303
+ Log.d(TAG, "getPreviewY: topMargin=" + margin + ", topInset=" + topInset + ", result=" + result);
1304
+ return result;
1305
+ }
1306
+ return previewContainer.getTop();
1307
+ }
1308
+ public int getPreviewWidth() {
1309
+ return previewContainer != null ? previewContainer.getWidth() : 0;
1310
+ }
1311
+ public int getPreviewHeight() {
1312
+ return previewContainer != null ? previewContainer.getHeight() : 0;
1313
+ }
1314
+ public void setPreviewSize(int x, int y, int width, int height) {
1315
+ setPreviewSize(x, y, width, height, null);
1316
+ }
1317
+
1318
+ public void setPreviewSize(int x, int y, int width, int height, Runnable callback) {
1319
+ if (previewContainer == null) {
1320
+ if (callback != null) callback.run();
1321
+ return;
1322
+ }
1323
+
1324
+ // Ensure this runs on the main UI thread
1325
+ mainExecutor.execute(() -> {
1326
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1327
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1328
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layoutParams;
1329
+
1330
+ // Only add insets for positioning coordinates, not for full-screen sizes
1331
+ int webViewTopInset = getWebViewTopInset();
1332
+ int webViewLeftInset = getWebViewLeftInset();
1333
+
1334
+ // Handle positioning - preserve current values if new values are not specified (negative)
1335
+ if (x >= 0) {
1336
+ // Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
1337
+ if (x == 0 && y == 0) {
1338
+ params.leftMargin = x;
1339
+ Log.d(TAG, "setPreviewSize: Full-screen mode - keeping x=0 without insets");
1340
+ } else {
1341
+ params.leftMargin = x + webViewLeftInset;
1342
+ Log.d(TAG, "setPreviewSize: Positioned mode - x=" + x + " + inset=" + webViewLeftInset + " = " + (x + webViewLeftInset));
1343
+ }
1344
+ }
1345
+ if (y >= 0) {
1346
+ // Don't add insets if this looks like a calculated full-screen coordinate (x=0, y=0)
1347
+ if (x == 0 && y == 0) {
1348
+ params.topMargin = y;
1349
+ Log.d(TAG, "setPreviewSize: Full-screen mode - keeping y=0 without insets");
1350
+ } else {
1351
+ params.topMargin = y + webViewTopInset;
1352
+ Log.d(TAG, "setPreviewSize: Positioned mode - y=" + y + " + inset=" + webViewTopInset + " = " + (y + webViewTopInset));
1353
+ }
1354
+ }
1355
+ if (width > 0) params.width = width;
1356
+ if (height > 0) params.height = height;
1357
+
1358
+ previewContainer.setLayoutParams(params);
1359
+ previewContainer.requestLayout();
1360
+
1361
+ Log.d(TAG, "setPreviewSize: Updated to " + params.width + "x" + params.height + " at (" + params.leftMargin + "," + params.topMargin + ")");
1362
+
1363
+ // Update session config to reflect actual layout
1364
+ if (sessionConfig != null) {
1365
+ String currentAspectRatio = sessionConfig.getAspectRatio();
1366
+
1367
+ // Calculate aspect ratio from actual dimensions if both width and height are provided
1368
+ String calculatedAspectRatio = currentAspectRatio;
1369
+ if (params.width > 0 && params.height > 0) {
1370
+ // Always use larger dimension / smaller dimension for consistent comparison
1371
+ float ratio = Math.max(params.width, params.height) / (float) Math.min(params.width, params.height);
1372
+ // Standard ratios: 16:9 ≈ 1.778, 4:3 ≈ 1.333
1373
+ float ratio16_9 = 16f / 9f; // 1.778
1374
+ float ratio4_3 = 4f / 3f; // 1.333
1375
+
1376
+ // Determine closest standard aspect ratio
1377
+ if (Math.abs(ratio - ratio16_9) < Math.abs(ratio - ratio4_3)) {
1378
+ calculatedAspectRatio = "16:9";
1379
+ } else {
1380
+ calculatedAspectRatio = "4:3";
1381
+ }
1382
+ Log.d(TAG, "setPreviewSize: Calculated aspect ratio from " + params.width + "x" + params.height + " = " + calculatedAspectRatio + " (normalized ratio=" + ratio + ")");
1383
+ }
1384
+
1385
+ sessionConfig = new CameraSessionConfiguration(
1386
+ sessionConfig.getDeviceId(),
1387
+ sessionConfig.getPosition(),
1388
+ params.leftMargin,
1389
+ params.topMargin,
1390
+ params.width,
1391
+ params.height,
1392
+ sessionConfig.getPaddingBottom(),
1393
+ sessionConfig.getToBack(),
1394
+ sessionConfig.getStoreToFile(),
1395
+ sessionConfig.getEnableOpacity(),
1396
+ sessionConfig.getEnableZoom(),
1397
+ sessionConfig.getDisableExifHeaderStripping(),
1398
+ sessionConfig.getDisableAudio(),
1399
+ sessionConfig.getZoomFactor(),
1400
+ calculatedAspectRatio,
1401
+ sessionConfig.getGridMode()
1402
+ );
1403
+
1404
+ // If aspect ratio changed due to size update, rebind camera
1405
+ if (isRunning && !Objects.equals(currentAspectRatio, calculatedAspectRatio)) {
1406
+ Log.d(TAG, "setPreviewSize: Aspect ratio changed from " + currentAspectRatio + " to " + calculatedAspectRatio + ", rebinding camera");
1407
+ bindCameraUseCases();
1408
+
1409
+ // Wait for camera rebinding to complete, then call callback
1410
+ if (callback != null) {
1411
+ previewContainer.post(() -> previewContainer.post(callback));
1412
+ }
1413
+ } else {
1414
+ // No camera rebinding needed, wait for layout to complete then call callback
1415
+ if (callback != null) {
1416
+ previewContainer.post(callback);
1417
+ }
1418
+ }
1419
+ } else {
1420
+ // No sessionConfig, just wait for layout then call callback
1421
+ if (callback != null) {
1422
+ previewContainer.post(callback);
1423
+ }
1424
+ }
1425
+ } else {
1426
+ Log.w(TAG, "setPreviewSize: Cannot set margins on layout params of type " + layoutParams.getClass().getSimpleName());
1427
+ // Fallback: just set width and height if specified
1428
+ if (width > 0) layoutParams.width = width;
1429
+ if (height > 0) layoutParams.height = height;
1430
+ previewContainer.setLayoutParams(layoutParams);
1431
+ previewContainer.requestLayout();
1432
+
1433
+ // Wait for layout then call callback
1434
+ if (callback != null) {
1435
+ previewContainer.post(callback);
1436
+ }
1437
+ }
1438
+ });
1439
+ }
1440
+
1441
+ private void updatePreviewLayoutForAspectRatio(String aspectRatio) {
1442
+ updatePreviewLayoutForAspectRatio(aspectRatio, null, null);
1443
+ }
1444
+
1445
+ private void updatePreviewLayoutForAspectRatio(String aspectRatio, Float x, Float y) {
1446
+ if (previewContainer == null || aspectRatio == null) return;
1447
+
1448
+ // Parse aspect ratio
1449
+ String[] ratios = aspectRatio.split(":");
1450
+ if (ratios.length != 2) return;
1451
+
1452
+ try {
1453
+ // For camera, use portrait orientation: 4:3 becomes 3:4, 16:9 becomes 9:16
1454
+ float ratio = Float.parseFloat(ratios[1]) / Float.parseFloat(ratios[0]);
1455
+
1456
+ // Get available space from webview dimensions
1457
+ int availableWidth = webView.getWidth();
1458
+ int availableHeight = webView.getHeight();
1459
+
1460
+ // Calculate position and size
1461
+ int finalX, finalY, finalWidth, finalHeight;
1462
+
1463
+ if (x != null && y != null) {
1464
+ // Account for WebView insets from edge-to-edge support
1465
+ int webViewTopInset = getWebViewTopInset();
1466
+ int webViewLeftInset = getWebViewLeftInset();
1467
+
1468
+ // Use provided coordinates with boundary checking, adjusted for insets
1469
+ finalX = Math.max(0, Math.min(x.intValue() + webViewLeftInset, availableWidth));
1470
+ finalY = Math.max(0, Math.min(y.intValue() + webViewTopInset, availableHeight));
1471
+
1472
+ // Calculate maximum available space from the given position
1473
+ int maxWidth = availableWidth - finalX;
1474
+ int maxHeight = availableHeight - finalY;
1475
+
1476
+ // Calculate optimal size while maintaining aspect ratio within available space
1477
+ finalWidth = maxWidth;
1478
+ finalHeight = (int) (maxWidth / ratio);
1479
+
1480
+ if (finalHeight > maxHeight) {
1481
+ // Height constraint is tighter, fit by height
1482
+ finalHeight = maxHeight;
1483
+ finalWidth = (int) (maxHeight * ratio);
1484
+ }
1485
+
1486
+ // Ensure final position stays within bounds
1487
+ finalX = Math.max(0, Math.min(finalX, availableWidth - finalWidth));
1488
+ finalY = Math.max(0, Math.min(finalY, availableHeight - finalHeight));
1489
+ } else {
1490
+ // Auto-center the view
1491
+ // Calculate size based on aspect ratio, using a reasonable base size
1492
+ // Use 80% of available space to ensure aspect ratio differences are visible
1493
+ int maxAvailableWidth = (int) (availableWidth * 0.8);
1494
+ int maxAvailableHeight = (int) (availableHeight * 0.8);
1495
+
1496
+ // Start with width-based calculation
1497
+ finalWidth = maxAvailableWidth;
1498
+ finalHeight = (int) (finalWidth / ratio);
1499
+
1500
+ // If height exceeds available space, use height-based calculation
1501
+ if (finalHeight > maxAvailableHeight) {
1502
+ finalHeight = maxAvailableHeight;
1503
+ finalWidth = (int) (finalHeight * ratio);
1504
+ }
1505
+
1506
+ // Center the view
1507
+ finalX = (availableWidth - finalWidth) / 2;
1508
+ finalY = (availableHeight - finalHeight) / 2;
1509
+
1510
+ Log.d(TAG, "updatePreviewLayoutForAspectRatio: Auto-center mode - ratio=" + ratio +
1511
+ ", calculated size=" + finalWidth + "x" + finalHeight +
1512
+ ", available=" + availableWidth + "x" + availableHeight);
1513
+ }
1514
+
1515
+ // Update layout params
1516
+ ViewGroup.LayoutParams currentParams = previewContainer.getLayoutParams();
1517
+ if (currentParams instanceof ViewGroup.MarginLayoutParams) {
1518
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) currentParams;
1519
+ params.width = finalWidth;
1520
+ params.height = finalHeight;
1521
+ params.leftMargin = finalX;
1522
+ params.topMargin = finalY;
1523
+ previewContainer.setLayoutParams(params);
1524
+ previewContainer.requestLayout();
1525
+ Log.d(TAG, "updatePreviewLayoutForAspectRatio: Updated to " + finalWidth + "x" + finalHeight + " at (" + finalX + "," + finalY + ")");
1526
+ }
1527
+ } catch (NumberFormatException e) {
1528
+ Log.e(TAG, "Invalid aspect ratio format: " + aspectRatio, e);
1529
+ }
1530
+ }
1531
+
1532
+ private void updatePreviewLayout() {
1533
+ if (previewContainer == null || sessionConfig == null) return;
1534
+
1535
+ String aspectRatio = sessionConfig.getAspectRatio();
1536
+ if (aspectRatio == null) return;
1537
+
1538
+ updatePreviewLayoutForAspectRatio(aspectRatio);
1539
+ }
1540
+
1541
+ private int getWebViewTopInset() {
1542
+ try {
1543
+ if (webView != null) {
1544
+ ViewGroup.LayoutParams layoutParams = webView.getLayoutParams();
1545
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1546
+ return ((ViewGroup.MarginLayoutParams) layoutParams).topMargin;
1547
+ }
1548
+ }
1549
+ } catch (Exception e) {
1550
+ Log.w(TAG, "Failed to get WebView top inset", e);
1551
+ }
1552
+ return 0;
1553
+ }
1554
+
1555
+ private int getWebViewLeftInset() {
1556
+ try {
1557
+ if (webView != null) {
1558
+ ViewGroup.LayoutParams layoutParams = webView.getLayoutParams();
1559
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1560
+ return ((ViewGroup.MarginLayoutParams) layoutParams).leftMargin;
1561
+ }
1562
+ }
1563
+ } catch (Exception e) {
1564
+ Log.w(TAG, "Failed to get WebView left inset", e);
1565
+ }
1566
+ return 0;
1567
+ }
1568
+
1569
+ /**
1570
+ * Get the current preview position and size in DP units (without insets)
1571
+ */
1572
+ public int[] getCurrentPreviewBounds() {
1573
+ if (previewContainer == null) {
1574
+ return new int[]{0, 0, 0, 0}; // x, y, width, height
1575
+ }
1576
+
1577
+ ViewGroup.LayoutParams layoutParams = previewContainer.getLayoutParams();
1578
+ int x = 0, y = 0, width = 0, height = 0;
1579
+
1580
+ if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
1581
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) layoutParams;
1582
+
1583
+ // Remove insets to get original coordinates in DP
1584
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
1585
+ float pixelRatio = metrics.density;
1586
+
1587
+ int webViewTopInset = getWebViewTopInset();
1588
+ int webViewLeftInset = getWebViewLeftInset();
1589
+
1590
+ x = Math.max(0, (int) ((params.leftMargin - webViewLeftInset) / pixelRatio));
1591
+ y = Math.max(0, (int) ((params.topMargin - webViewTopInset) / pixelRatio));
1592
+ width = (int) (params.width / pixelRatio);
1593
+ height = (int) (params.height / pixelRatio);
1101
1594
  }
1595
+
1596
+ return new int[]{x, y, width, height};
1102
1597
  }
1103
1598
  }
@@ -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