@capgo/camera-preview 7.4.0-beta.1 → 7.4.0-beta.10

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 (31) hide show
  1. package/README.md +195 -31
  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/file-system.probe +0 -0
  12. package/android/build.gradle +3 -1
  13. package/android/src/main/AndroidManifest.xml +5 -3
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +282 -45
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +902 -102
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +82 -0
  17. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +19 -5
  18. package/dist/docs.json +235 -6
  19. package/dist/esm/definitions.d.ts +119 -3
  20. package/dist/esm/definitions.js.map +1 -1
  21. package/dist/esm/web.d.ts +47 -3
  22. package/dist/esm/web.js +262 -78
  23. package/dist/esm/web.js.map +1 -1
  24. package/dist/plugin.cjs.js +258 -78
  25. package/dist/plugin.cjs.js.map +1 -1
  26. package/dist/plugin.js +258 -78
  27. package/dist/plugin.js.map +1 -1
  28. package/ios/Sources/CapgoCameraPreview/CameraController.swift +245 -28
  29. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  30. package/ios/Sources/CapgoCameraPreview/Plugin.swift +657 -90
  31. package/package.json +1 -1
@@ -3,15 +3,11 @@ package com.ahm.capacitor.camera.preview;
3
3
  import static android.Manifest.permission.CAMERA;
4
4
  import static android.Manifest.permission.RECORD_AUDIO;
5
5
 
6
+ import android.Manifest;
6
7
  import android.content.pm.ActivityInfo;
7
- import android.graphics.Point;
8
8
  import android.util.DisplayMetrics;
9
- import android.util.TypedValue;
10
- import android.view.Display;
11
- import androidx.annotation.NonNull;
12
9
  import com.getcapacitor.JSArray;
13
10
  import com.getcapacitor.JSObject;
14
- import com.getcapacitor.Logger;
15
11
  import com.getcapacitor.PermissionState;
16
12
  import com.getcapacitor.Plugin;
17
13
  import com.getcapacitor.PluginCall;
@@ -27,6 +23,14 @@ import java.util.Objects;
27
23
  import android.util.Size;
28
24
  import android.util.Log;
29
25
  import com.ahm.capacitor.camera.preview.model.LensInfo;
26
+ import com.google.android.gms.location.FusedLocationProviderClient;
27
+ import com.google.android.gms.location.LocationServices;
28
+ import org.json.JSONObject;
29
+ import android.location.Location;
30
+ import android.view.ViewGroup;
31
+
32
+ import com.getcapacitor.Logger;
33
+
30
34
 
31
35
  @CapacitorPlugin(
32
36
  name = "CameraPreview",
@@ -39,6 +43,10 @@ import com.ahm.capacitor.camera.preview.model.LensInfo;
39
43
  strings = { CAMERA },
40
44
  alias = CameraPreview.CAMERA_ONLY_PERMISSION_ALIAS
41
45
  ),
46
+ @Permission(
47
+ strings = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION },
48
+ alias = CameraPreview.CAMERA_WITH_LOCATION_PERMISSION_ALIAS
49
+ )
42
50
  }
43
51
  )
44
52
  public class CameraPreview
@@ -47,12 +55,15 @@ public class CameraPreview
47
55
 
48
56
  static final String CAMERA_WITH_AUDIO_PERMISSION_ALIAS = "cameraWithAudio";
49
57
  static final String CAMERA_ONLY_PERMISSION_ALIAS = "cameraOnly";
58
+ static final String CAMERA_WITH_LOCATION_PERMISSION_ALIAS = "cameraWithLocation";
50
59
 
51
60
  private String captureCallbackId = "";
52
61
  private String snapshotCallbackId = "";
53
62
  private String cameraStartCallbackId = "";
54
63
  private int previousOrientationRequest = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
55
64
  private CameraXView cameraXView;
65
+ private FusedLocationProviderClient fusedLocationClient;
66
+ private Location lastLocation;
56
67
 
57
68
  @PluginMethod
58
69
  public void start(PluginCall call) {
@@ -83,15 +94,62 @@ public class CameraPreview
83
94
  }
84
95
 
85
96
  @PluginMethod
86
- public void capture(PluginCall call) {
97
+ public void capture(final PluginCall call) {
87
98
  if (cameraXView == null || !cameraXView.isRunning()) {
88
99
  call.reject("Camera is not running");
89
100
  return;
90
101
  }
102
+
103
+ final boolean withExifLocation = call.getBoolean("withExifLocation", false);
104
+
105
+ if (withExifLocation) {
106
+ if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) != PermissionState.GRANTED) {
107
+ requestPermissionForAlias(CAMERA_WITH_LOCATION_PERMISSION_ALIAS, call, "captureWithLocationPermission");
108
+ } else {
109
+ getLocationAndCapture(call);
110
+ }
111
+ } else {
112
+ captureWithoutLocation(call);
113
+ }
114
+ }
115
+
116
+ @PermissionCallback
117
+ private void captureWithLocationPermission(PluginCall call) {
118
+ if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) == PermissionState.GRANTED) {
119
+ getLocationAndCapture(call);
120
+ } else {
121
+ Logger.warn("Location permission denied. Capturing photo without location data.");
122
+ captureWithoutLocation(call);
123
+ }
124
+ }
125
+
126
+ private void getLocationAndCapture(PluginCall call) {
127
+ if (fusedLocationClient == null) {
128
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(getContext());
129
+ }
130
+ fusedLocationClient.getLastLocation().addOnSuccessListener(getActivity(), location -> {
131
+ lastLocation = location;
132
+ proceedWithCapture(call, lastLocation);
133
+ }).addOnFailureListener(e -> {
134
+ Logger.error("Failed to get location: " + e.getMessage());
135
+ proceedWithCapture(call, null);
136
+ });
137
+ }
138
+
139
+ private void captureWithoutLocation(PluginCall call) {
140
+ proceedWithCapture(call, null);
141
+ }
142
+
143
+ private void proceedWithCapture(PluginCall call, Location location) {
91
144
  bridge.saveCall(call);
92
145
  captureCallbackId = call.getCallbackId();
146
+
93
147
  Integer quality = Objects.requireNonNull(call.getInt("quality", 85));
94
- cameraXView.capturePhoto(quality);
148
+ final boolean saveToGallery = call.getBoolean("saveToGallery", false);
149
+ Integer width = call.getInt("width");
150
+ Integer height = call.getInt("height");
151
+
152
+ cameraXView.capturePhoto(quality, saveToGallery, width, height, location);
95
153
  }
96
154
 
97
155
  @PluginMethod
@@ -235,7 +293,7 @@ public class CameraPreview
235
293
  }
236
294
  rear.put("supportedPictureSizes", rearSizesJs);
237
295
  supportedPictureSizesResult.put(rear);
238
-
296
+
239
297
  List<Size> frontSizes = CameraXView.getSupportedPictureSizes("front");
240
298
  JSObject front = new JSObject();
241
299
  front.put("facing", "front");
@@ -248,7 +306,7 @@ public class CameraPreview
248
306
  }
249
307
  front.put("supportedPictureSizes", frontSizesJs);
250
308
  supportedPictureSizesResult.put(front);
251
-
309
+
252
310
  JSObject ret = new JSObject();
253
311
  ret.put("supportedPictureSizes", supportedPictureSizesResult);
254
312
  call.resolve(ret);
@@ -326,14 +384,22 @@ public class CameraPreview
326
384
  final int width = call.getInt("width", 0);
327
385
  final int height = call.getInt("height", 0);
328
386
  final int paddingBottom = call.getInt("paddingBottom", 0);
329
- final boolean toBack = call.getBoolean("toBack", true);
330
- final boolean storeToFile = call.getBoolean("storeToFile", false);
331
- final boolean enableOpacity = call.getBoolean("enableOpacity", false);
332
- final boolean enableZoom = call.getBoolean("enableZoom", false);
333
- final boolean disableExifHeaderStripping = call.getBoolean("disableExifHeaderStripping", false);
334
- final boolean lockOrientation = call.getBoolean("lockAndroidOrientation", false);
335
- final boolean disableAudio = call.getBoolean("disableAudio", true);
387
+ final boolean toBack = Boolean.TRUE.equals(call.getBoolean("toBack", true));
388
+ final boolean storeToFile = Boolean.TRUE.equals(call.getBoolean("storeToFile", false));
389
+ final boolean enableOpacity = Boolean.TRUE.equals(call.getBoolean("enableOpacity", false));
390
+ final boolean enableZoom = Boolean.TRUE.equals(call.getBoolean("enableZoom", false));
391
+ final boolean disableExifHeaderStripping = Boolean.TRUE.equals(call.getBoolean("disableExifHeaderStripping", false));
392
+ final boolean lockOrientation = Boolean.TRUE.equals(call.getBoolean("lockAndroidOrientation", false));
393
+ final boolean disableAudio = Boolean.TRUE.equals(call.getBoolean("disableAudio", true));
394
+ final String aspectRatio = call.getString("aspectRatio", "4:3");
395
+ final String gridMode = call.getString("gridMode", "none");
336
396
 
397
+ // Check for conflict between aspectRatio and size
398
+ if (call.getData().has("aspectRatio") && (call.getData().has("width") || call.getData().has("height"))) {
399
+ call.reject("Cannot set both aspectRatio and size (width/height). Use setPreviewSize after start.");
400
+ return;
401
+ }
402
+
337
403
  float targetZoom = 1.0f;
338
404
  // Check if the selected device is a physical ultra-wide
339
405
  if (originalDeviceId != null) {
@@ -345,7 +411,7 @@ public class CameraPreview
345
411
  Log.d("CameraPreview", "Ultra-wide lens selected. Targeting 0.5x zoom on logical camera.");
346
412
  targetZoom = 0.5f;
347
413
  // Force the use of the logical camera by clearing the specific deviceId
348
- deviceId = null;
414
+ deviceId = null;
349
415
  break;
350
416
  }
351
417
  }
@@ -365,15 +431,50 @@ public class CameraPreview
365
431
  if (lockOrientation) {
366
432
  getBridge().getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
367
433
  }
368
- int computedX = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, x, metrics);
369
- int computedY = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, y, metrics);
370
- int computedWidth = width != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics) : (int) getBridge().getWebView().getWidth();
371
- int computedHeight = height != 0 ? (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics) : (int) getBridge().getWebView().getHeight();
372
- computedHeight -= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, metrics);
373
-
374
- CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f);
375
- config.setTargetZoom(finalTargetZoom);
376
434
 
435
+ // Debug: Let's check all the positioning information
436
+ ViewGroup webViewParent = (ViewGroup) getBridge().getWebView().getParent();
437
+
438
+ // Get webview position in different coordinate systems
439
+ int[] webViewLocationInWindow = new int[2];
440
+ int[] webViewLocationOnScreen = new int[2];
441
+ getBridge().getWebView().getLocationInWindow(webViewLocationInWindow);
442
+ getBridge().getWebView().getLocationOnScreen(webViewLocationOnScreen);
443
+
444
+ int webViewLeft = getBridge().getWebView().getLeft();
445
+ int webViewTop = getBridge().getWebView().getTop();
446
+
447
+ // Check parent position too
448
+ int[] parentLocationInWindow = new int[2];
449
+ int[] parentLocationOnScreen = new int[2];
450
+ webViewParent.getLocationInWindow(parentLocationInWindow);
451
+ webViewParent.getLocationOnScreen(parentLocationOnScreen);
452
+
453
+ // Calculate pixel ratio
454
+ float pixelRatio = metrics.density;
455
+
456
+ // Try using just the pixel ratio without any webview offset for now
457
+ int computedX = (int) (x * pixelRatio);
458
+ int computedY = (int) (y * pixelRatio);
459
+
460
+ Log.d("CameraPreview", "=== COORDINATE DEBUG ===");
461
+ Log.d("CameraPreview", "WebView getLeft/getTop: (" + webViewLeft + ", " + webViewTop + ")");
462
+ Log.d("CameraPreview", "WebView locationInWindow: (" + webViewLocationInWindow[0] + ", " + webViewLocationInWindow[1] + ")");
463
+ Log.d("CameraPreview", "WebView locationOnScreen: (" + webViewLocationOnScreen[0] + ", " + webViewLocationOnScreen[1] + ")");
464
+ Log.d("CameraPreview", "Parent locationInWindow: (" + parentLocationInWindow[0] + ", " + parentLocationInWindow[1] + ")");
465
+ Log.d("CameraPreview", "Parent locationOnScreen: (" + parentLocationOnScreen[0] + ", " + parentLocationOnScreen[1] + ")");
466
+ Log.d("CameraPreview", "Parent class: " + webViewParent.getClass().getSimpleName());
467
+ Log.d("CameraPreview", "Requested position (logical): (" + x + ", " + y + ")");
468
+ Log.d("CameraPreview", "Pixel ratio: " + pixelRatio);
469
+ Log.d("CameraPreview", "Final computed position (no offset): (" + computedX + ", " + computedY + ")");
470
+ Log.d("CameraPreview", "========================");
471
+ int computedWidth = width != 0 ? (int) (width * pixelRatio) : getBridge().getWebView().getWidth();
472
+ int computedHeight = height != 0 ? (int) (height * pixelRatio) : getBridge().getWebView().getHeight();
473
+ computedHeight -= (int) (paddingBottom * pixelRatio);
474
+
475
+ CameraSessionConfiguration config = new CameraSessionConfiguration(finalDeviceId, position, computedX, computedY, computedWidth, computedHeight, paddingBottom, toBack, storeToFile, enableOpacity, enableZoom, disableExifHeaderStripping, disableAudio, 1.0f, aspectRatio, gridMode);
476
+ config.setTargetZoom(finalTargetZoom);
477
+
377
478
  bridge.saveCall(call);
378
479
  cameraStartCallbackId = call.getCallbackId();
379
480
  cameraXView.startSession(config);
@@ -382,44 +483,180 @@ public class CameraPreview
382
483
  }
383
484
 
384
485
  @Override
385
- public void onPictureTaken(String result) {
386
- JSObject jsObject = new JSObject();
387
- jsObject.put("value", result);
388
- bridge.getSavedCall(captureCallbackId).resolve(jsObject);
486
+ public void onPictureTaken(String base64, JSONObject exif) {
487
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
488
+ if (pluginCall == null) {
489
+ Log.e("CameraPreview", "onPictureTaken: captureCallbackId is null");
490
+ return;
491
+ }
492
+ JSObject result = new JSObject();
493
+ result.put("value", base64);
494
+ result.put("exif", exif);
495
+ pluginCall.resolve(result);
496
+ bridge.releaseCall(pluginCall);
389
497
  }
390
498
 
391
499
  @Override
392
500
  public void onPictureTakenError(String message) {
393
- bridge.getSavedCall(captureCallbackId).reject(message);
501
+ PluginCall pluginCall = bridge.getSavedCall(captureCallbackId);
502
+ if (pluginCall == null) {
503
+ Log.e("CameraPreview", "onPictureTakenError: captureCallbackId is null");
504
+ return;
505
+ }
506
+ pluginCall.reject(message);
507
+ bridge.releaseCall(pluginCall);
508
+ }
509
+
510
+ @Override
511
+ public void onCameraStarted(int width, int height, int x, int y) {
512
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
513
+ if (call != null) {
514
+ // Convert pixel values back to logical units
515
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
516
+ float pixelRatio = metrics.density;
517
+
518
+ JSObject result = new JSObject();
519
+ result.put("width", width / pixelRatio);
520
+ result.put("height", height / pixelRatio);
521
+ result.put("x", x / pixelRatio);
522
+ result.put("y", y / pixelRatio);
523
+ call.resolve(result);
524
+ bridge.releaseCall(call);
525
+ cameraStartCallbackId = null; // Prevent re-use
526
+ }
394
527
  }
395
528
 
396
529
  @Override
397
530
  public void onSampleTaken(String result) {
398
- JSObject jsObject = new JSObject();
399
- jsObject.put("value", result);
400
- bridge.getSavedCall(snapshotCallbackId).resolve(jsObject);
531
+ // Handle sample taken if needed
532
+ Log.i("CameraPreview", "Sample taken: " + result);
401
533
  }
402
534
 
403
535
  @Override
404
536
  public void onSampleTakenError(String message) {
405
- bridge.getSavedCall(snapshotCallbackId).reject(message);
537
+ // Handle sample taken error if needed
538
+ Log.e("CameraPreview", "Sample taken error: " + message);
406
539
  }
407
540
 
408
541
  @Override
409
- public void onCameraStarted() {
410
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
411
- if (pluginCall != null) {
412
- pluginCall.resolve();
413
- bridge.releaseCall(pluginCall);
542
+ public void onCameraStartError(String message) {
543
+ PluginCall call = bridge.getSavedCall(cameraStartCallbackId);
544
+ if (call != null) {
545
+ call.reject(message);
546
+ bridge.releaseCall(call);
547
+ cameraStartCallbackId = null;
414
548
  }
415
549
  }
416
550
 
417
- @Override
418
- public void onCameraStartError(String message) {
419
- PluginCall pluginCall = bridge.getSavedCall(cameraStartCallbackId);
420
- if (pluginCall != null) {
421
- pluginCall.reject(message);
422
- bridge.releaseCall(pluginCall);
551
+ @PluginMethod
552
+ public void setAspectRatio(PluginCall call) {
553
+ if (cameraXView == null || !cameraXView.isRunning()) {
554
+ call.reject("Camera is not running");
555
+ return;
556
+ }
557
+ String aspectRatio = call.getString("aspectRatio", "4:3");
558
+ Float x = call.getFloat("x");
559
+ Float y = call.getFloat("y");
560
+
561
+ getActivity().runOnUiThread(() -> {
562
+ cameraXView.setAspectRatio(aspectRatio, x, y, () -> {
563
+ // Return the actual preview bounds after layout and camera operations are complete
564
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
565
+ JSObject ret = new JSObject();
566
+ ret.put("x", bounds[0]);
567
+ ret.put("y", bounds[1]);
568
+ ret.put("width", bounds[2]);
569
+ ret.put("height", bounds[3]);
570
+ call.resolve(ret);
571
+ });
572
+ });
573
+ }
574
+
575
+ @PluginMethod
576
+ public void getAspectRatio(PluginCall call) {
577
+ if (cameraXView == null || !cameraXView.isRunning()) {
578
+ call.reject("Camera is not running");
579
+ return;
580
+ }
581
+ String aspectRatio = cameraXView.getAspectRatio();
582
+ JSObject ret = new JSObject();
583
+ ret.put("aspectRatio", aspectRatio);
584
+ call.resolve(ret);
585
+ }
586
+
587
+ @PluginMethod
588
+ public void setGridMode(PluginCall call) {
589
+ if (cameraXView == null || !cameraXView.isRunning()) {
590
+ call.reject("Camera is not running");
591
+ return;
592
+ }
593
+ String gridMode = call.getString("gridMode", "none");
594
+ getActivity().runOnUiThread(() -> {
595
+ cameraXView.setGridMode(gridMode);
596
+ call.resolve();
597
+ });
598
+ }
599
+
600
+ @PluginMethod
601
+ public void getGridMode(PluginCall call) {
602
+ if (cameraXView == null || !cameraXView.isRunning()) {
603
+ call.reject("Camera is not running");
604
+ return;
605
+ }
606
+ JSObject ret = new JSObject();
607
+ ret.put("gridMode", cameraXView.getGridMode());
608
+ call.resolve(ret);
609
+ }
610
+
611
+ @PluginMethod
612
+ public void getPreviewSize(PluginCall call) {
613
+ if (cameraXView == null || !cameraXView.isRunning()) {
614
+ call.reject("Camera is not running");
615
+ return;
423
616
  }
617
+
618
+ // Convert pixel values back to logical units
619
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
620
+ float pixelRatio = metrics.density;
621
+
622
+ JSObject ret = new JSObject();
623
+ ret.put("x", cameraXView.getPreviewX() / pixelRatio);
624
+ ret.put("y", cameraXView.getPreviewY() / pixelRatio);
625
+ ret.put("width", cameraXView.getPreviewWidth() / pixelRatio);
626
+ ret.put("height", cameraXView.getPreviewHeight() / pixelRatio);
627
+ call.resolve(ret);
628
+ }
629
+ @PluginMethod
630
+ public void setPreviewSize(PluginCall call) {
631
+ if (cameraXView == null || !cameraXView.isRunning()) {
632
+ call.reject("Camera is not running");
633
+ return;
634
+ }
635
+
636
+ // Get values from call - null values will become 0
637
+ Integer xParam = call.getInt("x");
638
+ Integer yParam = call.getInt("y");
639
+ Integer widthParam = call.getInt("width");
640
+ Integer heightParam = call.getInt("height");
641
+
642
+ // Apply pixel ratio conversion to non-null values
643
+ DisplayMetrics metrics = getBridge().getActivity().getResources().getDisplayMetrics();
644
+ float pixelRatio = metrics.density;
645
+
646
+ int x = (xParam != null && xParam > 0) ? (int) (xParam * pixelRatio) : 0;
647
+ int y = (yParam != null && yParam > 0) ? (int) (yParam * pixelRatio) : 0;
648
+ int width = (widthParam != null && widthParam > 0) ? (int) (widthParam * pixelRatio) : 0;
649
+ int height = (heightParam != null && heightParam > 0) ? (int) (heightParam * pixelRatio) : 0;
650
+
651
+ cameraXView.setPreviewSize(x, y, width, height, () -> {
652
+ // Return the actual preview bounds after layout operations are complete
653
+ int[] bounds = cameraXView.getCurrentPreviewBounds();
654
+ JSObject ret = new JSObject();
655
+ ret.put("x", bounds[0]);
656
+ ret.put("y", bounds[1]);
657
+ ret.put("width", bounds[2]);
658
+ ret.put("height", bounds[3]);
659
+ call.resolve(ret);
660
+ });
424
661
  }
425
662
  }