@capgo/camera-preview 8.11.4 → 8.11.6

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.
@@ -40,6 +40,9 @@ android {
40
40
  lintOptions {
41
41
  abortOnError = false
42
42
  }
43
+ testOptions {
44
+ unitTests.includeAndroidResources = true
45
+ }
43
46
  }
44
47
 
45
48
  repositories {
@@ -69,6 +72,8 @@ dependencies {
69
72
  implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1'
70
73
 
71
74
  testImplementation "junit:junit:$junitVersion"
75
+ testImplementation 'org.robolectric:robolectric:4.14.1'
76
+ testImplementation 'androidx.test:core:1.6.1'
72
77
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
73
78
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
74
79
  }
@@ -29,7 +29,6 @@ import android.view.ViewGroup;
29
29
  import android.webkit.WebView;
30
30
  import androidx.annotation.RequiresPermission;
31
31
  import androidx.appcompat.app.AlertDialog;
32
- import androidx.core.app.ActivityCompat;
33
32
  import androidx.core.graphics.Insets;
34
33
  import androidx.core.view.ViewCompat;
35
34
  import androidx.core.view.WindowInsetsCompat;
@@ -456,10 +455,13 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
456
455
  final boolean withExifLocation = Boolean.TRUE.equals(call.getBoolean("withExifLocation", false));
457
456
 
458
457
  if (withExifLocation) {
459
- if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) != PermissionState.GRANTED) {
458
+ if (LocationPermissionHelper.canCaptureWithExifLocation(getContext())) {
459
+ getLocationAndCapture(call);
460
+ } else if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) != PermissionState.GRANTED) {
460
461
  requestPermissionForAlias(CAMERA_WITH_LOCATION_PERMISSION_ALIAS, call, "captureWithLocationPermission");
461
462
  } else {
462
- getLocationAndCapture(call);
463
+ Logger.warn("Location permission unavailable. Capturing photo without location data.");
464
+ captureWithoutLocation(call);
463
465
  }
464
466
  } else {
465
467
  captureWithoutLocation(call);
@@ -469,35 +471,40 @@ public class CameraPreview extends Plugin implements CameraXView.CameraXViewList
469
471
  @SuppressLint("MissingPermission")
470
472
  @PermissionCallback
471
473
  private void captureWithLocationPermission(PluginCall call) {
472
- if (getPermissionState(CAMERA_WITH_LOCATION_PERMISSION_ALIAS) == PermissionState.GRANTED) {
473
- if (
474
- ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) !=
475
- PackageManager.PERMISSION_GRANTED ||
476
- ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) !=
477
- PackageManager.PERMISSION_GRANTED
478
- ) {
479
- return;
480
- }
474
+ if (LocationPermissionHelper.canCaptureWithExifLocation(getContext())) {
481
475
  getLocationAndCapture(call);
482
476
  } else {
483
- Logger.warn("Location permission denied. Capturing photo without location data.");
477
+ Logger.warn("Location permission denied or unavailable. Capturing photo without location data.");
484
478
  captureWithoutLocation(call);
485
479
  }
486
480
  }
487
481
 
488
- @RequiresPermission(allOf = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION })
482
+ @RequiresPermission(anyOf = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION })
489
483
  private void getLocationAndCapture(PluginCall call) {
484
+ if (getActivity() == null) {
485
+ Logger.warn("Activity unavailable for location lookup. Capturing photo without location data.");
486
+ captureWithoutLocation(call);
487
+ return;
488
+ }
489
+
490
490
  if (fusedLocationClient == null) {
491
491
  fusedLocationClient = LocationServices.getFusedLocationProviderClient(getContext());
492
492
  }
493
493
  fusedLocationClient
494
494
  .getLastLocation()
495
- .addOnSuccessListener(getActivity(), (location) -> {
496
- lastLocation = location;
497
- proceedWithCapture(call, lastLocation);
498
- })
499
- .addOnFailureListener((e) -> {
500
- Logger.error("Failed to get location: " + e.getMessage());
495
+ .addOnCompleteListener((task) -> {
496
+ if (task.isCanceled()) {
497
+ Logger.warn("Location lookup cancelled. Capturing photo without location data.");
498
+ proceedWithCapture(call, null);
499
+ return;
500
+ }
501
+ if (task.isSuccessful()) {
502
+ lastLocation = task.getResult();
503
+ proceedWithCapture(call, lastLocation);
504
+ return;
505
+ }
506
+ Exception error = task.getException();
507
+ Logger.error("Failed to get location: " + (error != null ? error.getMessage() : "unknown error"));
501
508
  proceedWithCapture(call, null);
502
509
  });
503
510
  }
@@ -0,0 +1,32 @@
1
+ package app.capgo.capacitor.camera.preview;
2
+
3
+ import android.Manifest;
4
+ import android.content.Context;
5
+ import android.content.pm.PackageManager;
6
+ import androidx.core.app.ActivityCompat;
7
+
8
+ /**
9
+ * Runtime location permission checks for EXIF capture.
10
+ * <p>
11
+ * Capacitor's cached {@link PermissionState} can diverge from the OS grant state; always
12
+ * verify both before requesting location and before settling a capture call.
13
+ */
14
+ public final class LocationPermissionHelper {
15
+
16
+ private LocationPermissionHelper() {}
17
+
18
+ public static boolean hasRuntimeLocationPermissions(Context context) {
19
+ return (
20
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
21
+ ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
22
+ );
23
+ }
24
+
25
+ public static boolean canCaptureWithExifLocation(Context context) {
26
+ return canCaptureWithExifLocation(hasRuntimeLocationPermissions(context));
27
+ }
28
+
29
+ static boolean canCaptureWithExifLocation(boolean hasRuntimeLocationPermissions) {
30
+ return hasRuntimeLocationPermissions;
31
+ }
32
+ }
@@ -0,0 +1,38 @@
1
+ package app.capgo.capacitor.camera.preview;
2
+
3
+ import static org.junit.Assert.assertFalse;
4
+ import static org.junit.Assert.assertTrue;
5
+ import static org.robolectric.Shadows.shadowOf;
6
+
7
+ import android.Manifest;
8
+ import android.app.Application;
9
+ import androidx.test.core.app.ApplicationProvider;
10
+ import org.junit.Test;
11
+ import org.junit.runner.RunWith;
12
+ import org.robolectric.RobolectricTestRunner;
13
+ import org.robolectric.annotation.Config;
14
+
15
+ @RunWith(RobolectricTestRunner.class)
16
+ @Config(sdk = 31)
17
+ public class LocationPermissionHelperContextTest {
18
+
19
+ @Test
20
+ public void canCaptureWithExifLocationContextOverloadAllowsFineOnly() {
21
+ Application application = (Application) ApplicationProvider.getApplicationContext();
22
+ shadowOf(application).grantPermissions(Manifest.permission.ACCESS_FINE_LOCATION);
23
+ assertTrue(LocationPermissionHelper.canCaptureWithExifLocation(application));
24
+ }
25
+
26
+ @Test
27
+ public void canCaptureWithExifLocationContextOverloadAllowsCoarseOnly() {
28
+ Application application = (Application) ApplicationProvider.getApplicationContext();
29
+ shadowOf(application).grantPermissions(Manifest.permission.ACCESS_COARSE_LOCATION);
30
+ assertTrue(LocationPermissionHelper.canCaptureWithExifLocation(application));
31
+ }
32
+
33
+ @Test
34
+ public void canCaptureWithExifLocationContextOverloadDeniesWhenNeitherGranted() {
35
+ Application application = (Application) ApplicationProvider.getApplicationContext();
36
+ assertFalse(LocationPermissionHelper.canCaptureWithExifLocation(application));
37
+ }
38
+ }
@@ -0,0 +1,25 @@
1
+ package app.capgo.capacitor.camera.preview;
2
+
3
+ import static org.junit.Assert.assertFalse;
4
+ import static org.junit.Assert.assertTrue;
5
+
6
+ import org.junit.Test;
7
+
8
+ public class LocationPermissionHelperTest {
9
+
10
+ @Test
11
+ public void canCaptureWithExifLocationUsesRuntimePermissionOnly() {
12
+ assertTrue(LocationPermissionHelper.canCaptureWithExifLocation(true));
13
+ assertFalse(LocationPermissionHelper.canCaptureWithExifLocation(false));
14
+ }
15
+
16
+ @Test
17
+ public void runtimeAccessAllowsExifCaptureRegardlessOfCapacitorCache() {
18
+ assertTrue(LocationPermissionHelper.canCaptureWithExifLocation(true));
19
+ }
20
+
21
+ @Test
22
+ public void missingRuntimePermissionsFallsBackToGpsLessCapture() {
23
+ assertFalse(LocationPermissionHelper.canCaptureWithExifLocation(false));
24
+ }
25
+ }
package/dist/esm/web.js CHANGED
@@ -67,7 +67,7 @@ export class CameraPreviewWeb extends WebPlugin {
67
67
  try {
68
68
  stream === null || stream === void 0 ? void 0 : stream.getTracks().forEach((t) => t.stop());
69
69
  }
70
- catch (_e) {
70
+ catch (_c) {
71
71
  /* no-op */
72
72
  }
73
73
  }
@@ -177,16 +177,13 @@ export class CameraPreviewWeb extends WebPlugin {
177
177
  const leftInset = parseInset(styles.paddingLeft);
178
178
  const rightInset = parseInset(styles.paddingRight);
179
179
  probe.remove();
180
- let top = 0;
181
- if (orientation === 1) {
182
- top = topInset;
183
- }
184
- else if (orientation === 2) {
185
- top = leftInset > 0 ? leftInset : rightInset;
186
- }
187
- else {
188
- top = Math.max(topInset, leftInset, rightInset);
189
- }
180
+ const top = orientation === 1
181
+ ? topInset
182
+ : orientation === 2
183
+ ? leftInset > 0
184
+ ? leftInset
185
+ : rightInset
186
+ : Math.max(topInset, leftInset, rightInset);
190
187
  return { orientation, top };
191
188
  }
192
189
  async getZoomButtonValues() {
@@ -382,7 +379,7 @@ export class CameraPreviewWeb extends WebPlugin {
382
379
  }
383
380
  this.videoElement.style.setProperty('top', `${y}px`, 'important');
384
381
  // Force a style recalculation
385
- this.videoElement.offsetHeight;
382
+ void this.videoElement.offsetHeight;
386
383
  console.log('Positioning video:', {
387
384
  positioning,
388
385
  viewportHeight: window.innerHeight,
@@ -873,7 +870,9 @@ export class CameraPreviewWeb extends WebPlugin {
873
870
  await video.play();
874
871
  }
875
872
  catch (error) {
876
- throw new Error(`Failed to flip camera: ${error}`);
873
+ const err = new Error(`Failed to flip camera: ${String(error)}`);
874
+ err.cause = error;
875
+ throw err;
877
876
  }
878
877
  }
879
878
  async setOpacity(_options) {
@@ -1064,7 +1063,9 @@ export class CameraPreviewWeb extends WebPlugin {
1064
1063
  });
1065
1064
  }
1066
1065
  catch (error) {
1067
- throw new Error(`Failed to set zoom: ${error}`);
1066
+ const err = new Error(`Failed to set zoom: ${String(error)}`);
1067
+ err.cause = error;
1068
+ throw err;
1068
1069
  }
1069
1070
  }
1070
1071
  async getFlashMode() {
@@ -1110,7 +1111,9 @@ export class CameraPreviewWeb extends WebPlugin {
1110
1111
  await video.play();
1111
1112
  }
1112
1113
  catch (error) {
1113
- throw new Error(`Failed to swap to device ${options.deviceId}: ${error}`);
1114
+ const err = new Error(`Failed to swap to device ${options.deviceId}: ${String(error)}`);
1115
+ err.cause = error;
1116
+ throw err;
1114
1117
  }
1115
1118
  }
1116
1119
  async getAspectRatio() {