@capgo/camera-preview 7.4.0-alpha.11 → 7.4.0-alpha.17

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 CHANGED
@@ -262,6 +262,8 @@ Documentation for the [uploader](https://github.com/Cap-go/capacitor-uploader)
262
262
  * [`setPreviewSize(...)`](#setpreviewsize)
263
263
  * [`setFocus(...)`](#setfocus)
264
264
  * [`addListener('screenResize', ...)`](#addlistenerscreenresize-)
265
+ * [`deleteFile(...)`](#deletefile)
266
+ * [`getSafeAreaInsets()`](#getsafeareainsets)
265
267
  * [Interfaces](#interfaces)
266
268
  * [Type Aliases](#type-aliases)
267
269
  * [Enums](#enums)
@@ -734,6 +736,41 @@ addListener(eventName: "screenResize", listenerFunc: (data: { width: number; hei
734
736
  --------------------
735
737
 
736
738
 
739
+ ### deleteFile(...)
740
+
741
+ ```typescript
742
+ deleteFile(options: { path: string; }) => Promise<{ success: boolean; }>
743
+ ```
744
+
745
+ Deletes a file at the given absolute path on the device.
746
+ Use this to quickly clean up temporary images created with `storeToFile`.
747
+ On web, this is not supported and will throw.
748
+
749
+ | Param | Type |
750
+ | ------------- | ------------------------------ |
751
+ | **`options`** | <code>{ path: string; }</code> |
752
+
753
+ **Returns:** <code>Promise&lt;{ success: boolean; }&gt;</code>
754
+
755
+ **Since:** 8.2.0
756
+
757
+ --------------------
758
+
759
+
760
+ ### getSafeAreaInsets()
761
+
762
+ ```typescript
763
+ getSafeAreaInsets() => Promise<SafeAreaInsets>
764
+ ```
765
+
766
+ Gets the safe area insets for Android devices.
767
+ Returns the top and bottom insets in dp and the current orientation.
768
+
769
+ **Returns:** <code>Promise&lt;<a href="#safeareainsets">SafeAreaInsets</a>&gt;</code>
770
+
771
+ --------------------
772
+
773
+
737
774
  ### Interfaces
738
775
 
739
776
 
@@ -874,6 +911,18 @@ Represents the detailed information of the currently active lens.
874
911
  | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
875
912
 
876
913
 
914
+ #### SafeAreaInsets
915
+
916
+ Represents safe area insets on Android.
917
+ Values are expressed in logical pixels (dp) to match JS layout units.
918
+
919
+ | Prop | Type | Description |
920
+ | ----------------- | ------------------- | ---------------------------------------------------------------- |
921
+ | **`orientation`** | <code>number</code> | Current device orientation as reported by Android configuration. |
922
+ | **`top`** | <code>number</code> | Top inset (e.g., status bar or cutout) in dp. |
923
+ | **`bottom`** | <code>number</code> | Bottom inset (e.g., navigation bar) in dp. |
924
+
925
+
877
926
  ### Type Aliases
878
927
 
879
928
 
@@ -5,13 +5,18 @@ 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;
8
9
  import android.location.Location;
9
10
  import android.util.DisplayMetrics;
10
11
  import android.util.Log;
11
12
  import android.util.Size;
13
+ import android.view.OrientationEventListener;
12
14
  import android.view.View;
13
15
  import android.view.ViewGroup;
14
16
  import android.webkit.WebView;
17
+ import androidx.core.graphics.Insets;
18
+ import androidx.core.view.ViewCompat;
19
+ import androidx.core.view.WindowInsetsCompat;
15
20
  import com.ahm.capacitor.camera.preview.model.CameraDevice;
16
21
  import com.ahm.capacitor.camera.preview.model.CameraSessionConfiguration;
17
22
  import com.ahm.capacitor.camera.preview.model.LensInfo;
@@ -69,6 +74,8 @@ public class CameraPreview
69
74
  private CameraXView cameraXView;
70
75
  private FusedLocationProviderClient fusedLocationClient;
71
76
  private Location lastLocation;
77
+ private OrientationEventListener orientationListener;
78
+ private int lastOrientation = Configuration.ORIENTATION_UNDEFINED;
72
79
 
73
80
  @PluginMethod
74
81
  public void start(PluginCall call) {
@@ -205,6 +212,13 @@ public class CameraPreview
205
212
  .getActivity()
206
213
  .setRequestedOrientation(previousOrientationRequest);
207
214
 
215
+ // Disable and clear orientation listener
216
+ if (orientationListener != null) {
217
+ orientationListener.disable();
218
+ orientationListener = null;
219
+ lastOrientation = Configuration.ORIENTATION_UNDEFINED;
220
+ }
221
+
208
222
  if (cameraXView != null && cameraXView.isRunning()) {
209
223
  cameraXView.stopSession();
210
224
  cameraXView = null;
@@ -215,6 +229,10 @@ public class CameraPreview
215
229
 
216
230
  @PluginMethod
217
231
  public void getSupportedFlashModes(PluginCall call) {
232
+ if (cameraXView == null || !cameraXView.isRunning()) {
233
+ call.reject("Camera is not running");
234
+ return;
235
+ }
218
236
  List<String> supportedFlashModes = cameraXView.getSupportedFlashModes();
219
237
  JSArray jsonFlashModes = new JSArray();
220
238
  for (String mode : supportedFlashModes) {
@@ -268,6 +286,10 @@ public class CameraPreview
268
286
 
269
287
  @PluginMethod
270
288
  public void getZoom(PluginCall call) {
289
+ if (cameraXView == null || !cameraXView.isRunning()) {
290
+ call.reject("Camera is not running");
291
+ return;
292
+ }
271
293
  ZoomFactors zoomFactors = cameraXView.getZoomFactors();
272
294
  JSObject result = new JSObject();
273
295
  result.put("min", zoomFactors.getMin());
@@ -278,6 +300,10 @@ public class CameraPreview
278
300
 
279
301
  @PluginMethod
280
302
  public void getZoomButtonValues(PluginCall call) {
303
+ if (cameraXView == null || !cameraXView.isRunning()) {
304
+ call.reject("Camera is not running");
305
+ return;
306
+ }
281
307
  // Build a sorted set to dedupe and order ascending
282
308
  java.util.Set<Double> sorted = new java.util.TreeSet<>();
283
309
  sorted.add(1.0);
@@ -288,15 +314,17 @@ public class CameraPreview
288
314
  List<CameraDevice> devices = CameraXView.getAvailableDevicesStatic(
289
315
  getContext()
290
316
  );
317
+ ZoomFactors zoomFactors = cameraXView.getZoomFactors();
291
318
  boolean hasUltraWide = false;
292
319
  boolean hasTelephoto = false;
293
320
  float minUltra = 0.5f;
321
+
294
322
  for (CameraDevice device : devices) {
295
323
  for (com.ahm.capacitor.camera.preview.model.LensInfo lens : device.getLenses()) {
296
324
  if ("ultraWide".equals(lens.getDeviceType())) {
297
325
  hasUltraWide = true;
298
326
  // Use overall minZoom for that device as the button value to represent UW
299
- minUltra = Math.min(minUltra, device.getMinZoom());
327
+ minUltra = Math.max(minUltra, zoomFactors.getMin());
300
328
  } else if ("telephoto".equals(lens.getDeviceType())) {
301
329
  hasTelephoto = true;
302
330
  }
@@ -332,9 +360,8 @@ public class CameraPreview
332
360
  call.reject("level parameter is required");
333
361
  return;
334
362
  }
335
- Boolean autoFocus = call.getBoolean("autoFocus", true);
336
363
  try {
337
- cameraXView.setZoom(level, autoFocus);
364
+ cameraXView.setZoom(level);
338
365
  call.resolve();
339
366
  } catch (Exception e) {
340
367
  call.reject("Failed to set zoom: " + e.getMessage());
@@ -967,6 +994,50 @@ public class CameraPreview
967
994
  bridge.saveCall(call);
968
995
  cameraStartCallbackId = call.getCallbackId();
969
996
  cameraXView.startSession(config);
997
+
998
+ // Setup orientation listener to mirror iOS screenResize emission
999
+ if (orientationListener == null) {
1000
+ lastOrientation = getContext()
1001
+ .getResources()
1002
+ .getConfiguration()
1003
+ .orientation;
1004
+ orientationListener = new OrientationEventListener(getContext()) {
1005
+ @Override
1006
+ public void onOrientationChanged(int orientation) {
1007
+ if (orientation == ORIENTATION_UNKNOWN) return;
1008
+ int current = getContext()
1009
+ .getResources()
1010
+ .getConfiguration()
1011
+ .orientation;
1012
+ if (current != lastOrientation) {
1013
+ lastOrientation = current;
1014
+ handleOrientationChange();
1015
+ }
1016
+ }
1017
+ };
1018
+ if (orientationListener.canDetectOrientation()) {
1019
+ orientationListener.enable();
1020
+ }
1021
+ }
1022
+ });
1023
+ }
1024
+
1025
+ private void handleOrientationChange() {
1026
+ if (cameraXView == null || !cameraXView.isRunning()) return;
1027
+ getBridge()
1028
+ .getActivity()
1029
+ .runOnUiThread(() -> {
1030
+ // Reapply current aspect ratio to recompute layout, then emit screenResize
1031
+ String ar = cameraXView.getAspectRatio();
1032
+ cameraXView.setAspectRatio(ar, null, null, () -> {
1033
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
1034
+ JSObject data = new JSObject();
1035
+ data.put("x", bounds[0]);
1036
+ data.put("y", bounds[1]);
1037
+ data.put("width", bounds[2]);
1038
+ data.put("height", bounds[3]);
1039
+ notifyListeners("screenResize", data);
1040
+ });
970
1041
  });
971
1042
  }
972
1043
 
@@ -1254,4 +1325,120 @@ public class CameraPreview
1254
1325
  call.reject("Failed to delete file: " + e.getMessage());
1255
1326
  }
1256
1327
  }
1328
+
1329
+ @PluginMethod
1330
+ public void getSafeAreaInsets(PluginCall call) {
1331
+ JSObject ret = new JSObject();
1332
+ int orientation = getContext()
1333
+ .getResources()
1334
+ .getConfiguration()
1335
+ .orientation;
1336
+
1337
+ int topPx = 0;
1338
+ int bottomPx = 0;
1339
+ try {
1340
+ View webView = getBridge().getWebView();
1341
+ if (webView != null) {
1342
+ DisplayMetrics metrics = getBridge()
1343
+ .getActivity()
1344
+ .getResources()
1345
+ .getDisplayMetrics();
1346
+ int screenHeight = metrics.heightPixels;
1347
+ int[] location = new int[2];
1348
+ webView.getLocationOnScreen(location);
1349
+ int webViewTop = location[1];
1350
+ int webViewBottom = webViewTop + webView.getHeight();
1351
+ int webViewBottomGap = Math.max(0, screenHeight - webViewBottom);
1352
+
1353
+ // System insets (status/navigation/cutout)
1354
+ int systemTop = 0;
1355
+ int systemBottom = 0;
1356
+ View decorView = getBridge().getActivity().getWindow().getDecorView();
1357
+ WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
1358
+ if (insets != null) {
1359
+ Insets sysBars = insets.getInsets(
1360
+ WindowInsetsCompat.Type.systemBars()
1361
+ );
1362
+ Insets cutout = insets.getInsets(
1363
+ WindowInsetsCompat.Type.displayCutout()
1364
+ );
1365
+ systemTop = Math.max(sysBars.top, cutout.top);
1366
+ systemBottom = Math.max(sysBars.bottom, cutout.bottom);
1367
+ } else {
1368
+ systemTop = getStatusBarHeightPx();
1369
+ systemBottom = getNavigationBarHeightPx();
1370
+ }
1371
+
1372
+ // Top: report the gap between screen and WebView (useful when not edge-to-edge)
1373
+ topPx = Math.max(0, webViewTop);
1374
+
1375
+ // Bottom logic:
1376
+ // - If WebView has a bottom gap equal to the system nav bar height (3-button mode),
1377
+ // it means layout already accounts for it -> return 0 as requested.
1378
+ // - If WebView has no gap (edge-to-edge or overlay), return system bottom inset.
1379
+ // - Otherwise, default to system bottom inset (avoid counting app UI like tab bars).
1380
+ if (
1381
+ webViewBottomGap > 0 && approxEqualPx(webViewBottomGap, systemBottom)
1382
+ ) {
1383
+ bottomPx = 0; // already offset by system nav bar
1384
+ } else if (webViewBottomGap == 0) {
1385
+ bottomPx = systemBottom;
1386
+ } else {
1387
+ bottomPx = systemBottom;
1388
+ }
1389
+ } else {
1390
+ // Fallback if WebView is unavailable
1391
+ View decorView = getBridge().getActivity().getWindow().getDecorView();
1392
+ WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(decorView);
1393
+ if (insets != null) {
1394
+ Insets sysBars = insets.getInsets(
1395
+ WindowInsetsCompat.Type.systemBars()
1396
+ );
1397
+ Insets cutout = insets.getInsets(
1398
+ WindowInsetsCompat.Type.displayCutout()
1399
+ );
1400
+ topPx = Math.max(sysBars.top, cutout.top);
1401
+ bottomPx = Math.max(sysBars.bottom, cutout.bottom);
1402
+ } else {
1403
+ topPx = getStatusBarHeightPx();
1404
+ bottomPx = getNavigationBarHeightPx();
1405
+ }
1406
+ }
1407
+ } catch (Exception e) {
1408
+ topPx = getStatusBarHeightPx();
1409
+ bottomPx = getNavigationBarHeightPx();
1410
+ }
1411
+
1412
+ float density = getContext().getResources().getDisplayMetrics().density;
1413
+ ret.put("orientation", orientation);
1414
+ ret.put("top", topPx / density);
1415
+ ret.put("bottom", bottomPx / density);
1416
+ call.resolve(ret);
1417
+ }
1418
+
1419
+ private boolean approxEqualPx(int a, int b) {
1420
+ return Math.abs(a - b) <= 2; // within 2px tolerance
1421
+ }
1422
+
1423
+ private int getStatusBarHeightPx() {
1424
+ int result = 0;
1425
+ int resourceId = getContext()
1426
+ .getResources()
1427
+ .getIdentifier("status_bar_height", "dimen", "android");
1428
+ if (resourceId > 0) {
1429
+ result = getContext().getResources().getDimensionPixelSize(resourceId);
1430
+ }
1431
+ return result;
1432
+ }
1433
+
1434
+ private int getNavigationBarHeightPx() {
1435
+ int result = 0;
1436
+ int resourceId = getContext()
1437
+ .getResources()
1438
+ .getIdentifier("navigation_bar_height", "dimen", "android");
1439
+ if (resourceId > 0) {
1440
+ result = getContext().getResources().getDimensionPixelSize(resourceId);
1441
+ }
1442
+ return result;
1443
+ }
1257
1444
  }
@@ -727,7 +727,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
727
727
  }
728
728
  }
729
729
 
730
- setZoomInternal(initialZoom);
730
+ setZoom(initialZoom);
731
731
  }
732
732
 
733
733
  isRunning = true;
@@ -1634,7 +1634,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1634
1634
  }
1635
1635
  }
1636
1636
 
1637
- public void setZoom(float zoomRatio, boolean autoFocus) throws Exception {
1637
+ public void setZoom(float zoomRatio) throws Exception {
1638
1638
  if (camera == null) {
1639
1639
  throw new Exception("Camera not initialized");
1640
1640
  }
@@ -1643,26 +1643,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1643
1643
 
1644
1644
  // Just let CameraX handle everything - it should automatically switch lenses
1645
1645
  try {
1646
- ListenableFuture<Void> zoomFuture = camera
1647
- .getCameraControl()
1648
- .setZoomRatio(zoomRatio);
1646
+ ZoomFactors zoomFactors = getZoomFactors();
1649
1647
 
1650
- // Add callback to see what actually happened
1651
- zoomFuture.addListener(
1652
- () -> {
1653
- try {
1654
- zoomFuture.get();
1655
- Log.d(TAG, "Zoom successfully set to " + zoomRatio);
1656
- // Trigger autofocus after zoom if requested
1657
- if (autoFocus) {
1658
- triggerAutoFocus();
1659
- }
1660
- } catch (Exception e) {
1661
- Log.e(TAG, "Error setting zoom: " + e.getMessage());
1662
- }
1663
- },
1664
- ContextCompat.getMainExecutor(context)
1665
- );
1648
+ if (zoomRatio < zoomFactors.getMin()) {
1649
+ zoomRatio = zoomFactors.getMin();
1650
+ } else if (zoomRatio > zoomFactors.getMax()) {
1651
+ zoomRatio = zoomFactors.getMax();
1652
+ }
1653
+
1654
+ camera.getCameraControl().setZoomRatio(zoomRatio);
1655
+ // Note: autofocus is intentionally not triggered on zoom because it's done by CameraX
1666
1656
  } catch (Exception e) {
1667
1657
  Log.e(TAG, "Failed to set zoom: " + e.getMessage());
1668
1658
  throw e;
@@ -1821,8 +1811,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1821
1811
 
1822
1812
  // Set initial state for smooth animation
1823
1813
  focusIndicatorView.setAlpha(1f); // Start visible
1824
- focusIndicatorView.setScaleX(1.8f); // Start larger for scale-in effect
1825
- focusIndicatorView.setScaleY(1.8f);
1814
+ focusIndicatorView.setScaleX(1.4f); // Start slightly larger for a quick scale-in
1815
+ focusIndicatorView.setScaleY(1.4f);
1826
1816
  focusIndicatorView.setVisibility(View.VISIBLE);
1827
1817
 
1828
1818
  // Ensure container doesn't intercept touch events
@@ -1848,16 +1838,16 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1848
1838
 
1849
1839
  // Smooth scale down animation with easing (no fade needed since we start visible)
1850
1840
  ScaleAnimation scaleAnimation = new ScaleAnimation(
1851
- 1.8f,
1841
+ 1.4f,
1852
1842
  1.0f,
1853
- 1.8f,
1843
+ 1.4f,
1854
1844
  1.0f,
1855
1845
  Animation.RELATIVE_TO_SELF,
1856
1846
  0.5f,
1857
1847
  Animation.RELATIVE_TO_SELF,
1858
1848
  0.5f
1859
1849
  );
1860
- scaleAnimation.setDuration(300);
1850
+ scaleAnimation.setDuration(120);
1861
1851
  scaleAnimation.setInterpolator(
1862
1852
  new android.view.animation.OvershootInterpolator(1.2f)
1863
1853
  );
@@ -1865,7 +1855,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1865
1855
  // Start the animation
1866
1856
  focusIndicatorView.startAnimation(scaleAnimation);
1867
1857
 
1868
- // Schedule fade out and removal with smoother timing
1858
+ // Schedule fast fade out and removal
1869
1859
  focusIndicatorView.postDelayed(
1870
1860
  new Runnable() {
1871
1861
  @Override
@@ -1876,121 +1866,39 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
1876
1866
  thisIndicatorView == focusIndicatorView &&
1877
1867
  thisAnimationId == focusIndicatorAnimationId
1878
1868
  ) {
1879
- // Smooth fade to semi-transparent
1880
- AlphaAnimation fadeToTransparent = new AlphaAnimation(1f, 0.4f);
1881
- fadeToTransparent.setDuration(400);
1882
- fadeToTransparent.setInterpolator(
1883
- new android.view.animation.AccelerateInterpolator()
1884
- );
1885
-
1886
- fadeToTransparent.setAnimationListener(
1887
- new Animation.AnimationListener() {
1888
- @Override
1889
- public void onAnimationStart(Animation animation) {
1890
- Log.d(TAG, "showFocusIndicator: Fade to transparent started");
1891
- }
1892
-
1893
- @Override
1894
- public void onAnimationEnd(Animation animation) {
1895
- Log.d(
1896
- TAG,
1897
- "showFocusIndicator: Fade to transparent ended, starting final fade out"
1898
- );
1899
- // Final smooth fade out and scale down
1900
- if (
1901
- focusIndicatorView != null &&
1902
- thisIndicatorView == focusIndicatorView &&
1903
- thisAnimationId == focusIndicatorAnimationId
1904
- ) {
1905
- AnimationSet finalAnimation = new AnimationSet(false);
1906
-
1907
- AlphaAnimation finalFadeOut = new AlphaAnimation(0.4f, 0f);
1908
- finalFadeOut.setDuration(500);
1909
- finalFadeOut.setStartOffset(300);
1910
- finalFadeOut.setInterpolator(
1911
- new android.view.animation.AccelerateInterpolator()
1912
- );
1913
-
1914
- ScaleAnimation finalScaleDown = new ScaleAnimation(
1915
- 1.0f,
1916
- 0.9f,
1917
- 1.0f,
1918
- 0.9f,
1919
- Animation.RELATIVE_TO_SELF,
1920
- 0.5f,
1921
- Animation.RELATIVE_TO_SELF,
1922
- 0.5f
1923
- );
1924
- finalScaleDown.setDuration(500);
1925
- finalScaleDown.setStartOffset(300);
1926
- finalScaleDown.setInterpolator(
1927
- new android.view.animation.AccelerateInterpolator()
1928
- );
1929
-
1930
- finalAnimation.addAnimation(finalFadeOut);
1931
- finalAnimation.addAnimation(finalScaleDown);
1932
-
1933
- finalAnimation.setAnimationListener(
1934
- new Animation.AnimationListener() {
1935
- @Override
1936
- public void onAnimationStart(Animation animation) {
1937
- Log.d(
1938
- TAG,
1939
- "showFocusIndicator: Final animation started"
1940
- );
1941
- }
1942
-
1943
- @Override
1944
- public void onAnimationEnd(Animation animation) {
1945
- Log.d(
1946
- TAG,
1947
- "showFocusIndicator: Final animation ended, removing indicator"
1948
- );
1949
- // Remove the focus indicator
1950
- if (
1951
- focusIndicatorView != null &&
1952
- previewContainer != null &&
1953
- thisIndicatorView == focusIndicatorView &&
1954
- thisAnimationId == focusIndicatorAnimationId
1955
- ) {
1956
- try {
1957
- focusIndicatorView.clearAnimation();
1958
- } catch (Exception ignore) {}
1959
- previewContainer.removeView(focusIndicatorView);
1960
- focusIndicatorView = null;
1961
- }
1962
- }
1963
-
1964
- @Override
1965
- public void onAnimationRepeat(Animation animation) {}
1966
- }
1967
- );
1968
-
1869
+ focusIndicatorView
1870
+ .animate()
1871
+ .alpha(0f)
1872
+ .scaleX(0.9f)
1873
+ .scaleY(0.9f)
1874
+ .setDuration(180)
1875
+ .setInterpolator(
1876
+ new android.view.animation.AccelerateInterpolator()
1877
+ )
1878
+ .withEndAction(
1879
+ new Runnable() {
1880
+ @Override
1881
+ public void run() {
1969
1882
  if (
1883
+ focusIndicatorView != null &&
1884
+ previewContainer != null &&
1970
1885
  thisIndicatorView == focusIndicatorView &&
1971
1886
  thisAnimationId == focusIndicatorAnimationId
1972
1887
  ) {
1973
- focusIndicatorView.startAnimation(finalAnimation);
1888
+ try {
1889
+ focusIndicatorView.clearAnimation();
1890
+ } catch (Exception ignore) {}
1891
+ previewContainer.removeView(focusIndicatorView);
1892
+ focusIndicatorView = null;
1974
1893
  }
1975
1894
  }
1976
1895
  }
1977
-
1978
- @Override
1979
- public void onAnimationRepeat(Animation animation) {}
1980
- }
1981
- );
1982
-
1983
- if (
1984
- thisIndicatorView == focusIndicatorView &&
1985
- thisAnimationId == focusIndicatorAnimationId
1986
- ) {
1987
- focusIndicatorView.startAnimation(fadeToTransparent);
1988
- }
1896
+ );
1989
1897
  }
1990
1898
  }
1991
1899
  },
1992
- 800
1993
- ); // Optimal timing for smooth focus feedback
1900
+ 250
1901
+ ); // Faster feedback
1994
1902
  }
1995
1903
 
1996
1904
  public static List<Size> getSupportedPictureSizes(String facing) {
@@ -2016,89 +1924,6 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
2016
1924
  return sizes;
2017
1925
  }
2018
1926
 
2019
- private void setZoomInternal(float zoomRatio) {
2020
- if (camera != null) {
2021
- try {
2022
- float minZoom = Objects.requireNonNull(
2023
- camera.getCameraInfo().getZoomState().getValue()
2024
- ).getMinZoomRatio();
2025
- float maxZoom = camera
2026
- .getCameraInfo()
2027
- .getZoomState()
2028
- .getValue()
2029
- .getMaxZoomRatio();
2030
- float currentZoom = camera
2031
- .getCameraInfo()
2032
- .getZoomState()
2033
- .getValue()
2034
- .getZoomRatio();
2035
-
2036
- Log.d(
2037
- TAG,
2038
- "setZoomInternal: Current camera range: " +
2039
- minZoom +
2040
- "-" +
2041
- maxZoom +
2042
- ", current: " +
2043
- currentZoom
2044
- );
2045
- Log.d(TAG, "setZoomInternal: Requesting zoom: " + zoomRatio);
2046
-
2047
- // Try to set zoom directly - let CameraX handle lens switching
2048
- ListenableFuture<Void> zoomFuture = camera
2049
- .getCameraControl()
2050
- .setZoomRatio(zoomRatio);
2051
-
2052
- zoomFuture.addListener(
2053
- () -> {
2054
- try {
2055
- zoomFuture.get(); // Check if zoom was successful
2056
- float newZoom = Objects.requireNonNull(
2057
- camera.getCameraInfo().getZoomState().getValue()
2058
- ).getZoomRatio();
2059
- Log.d(
2060
- TAG,
2061
- "setZoomInternal: Zoom set successfully to " +
2062
- newZoom +
2063
- " (requested: " +
2064
- zoomRatio +
2065
- ")"
2066
- );
2067
-
2068
- // Check if CameraX switched cameras
2069
- String newCameraId = getCameraId(camera.getCameraInfo());
2070
- if (!newCameraId.equals(currentDeviceId)) {
2071
- currentDeviceId = newCameraId;
2072
- Log.d(
2073
- TAG,
2074
- "setZoomInternal: CameraX switched to camera: " + newCameraId
2075
- );
2076
- }
2077
- } catch (Exception e) {
2078
- Log.w(
2079
- TAG,
2080
- "setZoomInternal: Zoom operation failed: " + e.getMessage()
2081
- );
2082
- // Fallback: clamp to current camera's range
2083
- float clampedZoom = Math.max(
2084
- minZoom,
2085
- Math.min(zoomRatio, maxZoom)
2086
- );
2087
- camera.getCameraControl().setZoomRatio(clampedZoom);
2088
- Log.d(
2089
- TAG,
2090
- "setZoomInternal: Fallback - clamped zoom to " + clampedZoom
2091
- );
2092
- }
2093
- },
2094
- mainExecutor
2095
- );
2096
- } catch (Exception e) {
2097
- Log.e(TAG, "setZoomInternal: Error setting zoom", e);
2098
- }
2099
- }
2100
- }
2101
-
2102
1927
  public static List<String> getSupportedFlashModesStatic() {
2103
1928
  try {
2104
1929
  // For static method, we can return common flash modes