@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 +49 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +190 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +40 -215
- package/dist/docs.json +68 -0
- package/dist/esm/definitions.d.ts +30 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +3 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +3 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +15 -3
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +2 -2
- package/package.json +1 -1
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<{ success: boolean; }></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<<a href="#safeareainsets">SafeAreaInsets</a>></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>() => Promise<void></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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1647
|
-
.getCameraControl()
|
|
1648
|
-
.setZoomRatio(zoomRatio);
|
|
1646
|
+
ZoomFactors zoomFactors = getZoomFactors();
|
|
1649
1647
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
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.
|
|
1825
|
-
focusIndicatorView.setScaleY(1.
|
|
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.
|
|
1841
|
+
1.4f,
|
|
1852
1842
|
1.0f,
|
|
1853
|
-
1.
|
|
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(
|
|
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
|
|
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
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1993
|
-
); //
|
|
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
|