@scr2em/capacitor-scanner 6.0.31 → 6.0.34

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,7 +262,7 @@ Set the camera zoom level.
262
262
 
263
263
  #### StartOptions
264
264
 
265
- <code>{ /** * Barcode formats to detect when barcodeDetection is enabled. */ formats?: BarcodeFormat[]; /** * Camera direction to use. Defaults to 'BACK'. */ cameraDirection?: 'BACK' | 'FRONT'; /** * Enable barcode detection on start. Defaults to false. * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events. */ barcodeDetection?: boolean; /** * Whether to show the highlight overlay for detected barcodes. Defaults to false. * Only applies when barcodeDetection is true. */ barcodeHighlight?: boolean; /** * Enable object detection (business card) on start. Defaults to false. * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events. */ objectDetection?: boolean; /** * Whether to show the highlight overlay for detected business cards. Defaults to true. * Only applies when objectDetection is true. */ objectHighlight?: boolean; /** * Minimum interval in seconds between consecutive 'rectangleDetected' events. * Only applies when objectDetection is true. * Defaults to 0 (no throttling beyond initial stability check). */ rectangleEmitIntervalSeconds?: number; /** * Optional regex pattern to filter scanned barcodes. * If provided, only barcodes matching this pattern will be reported. */ regex?: string; /** * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline). */ regexFlags?: string; /** * Duration in seconds to pause rectangle detection after a barcode is successfully scanned. * When both barcode and object detection are enabled, this gives barcode scanning priority * by temporarily suppressing rectangle detection after a barcode is emitted. * Defaults to 0 (no throttle, both detections run simultaneously). */ detectionThrottleSeconds?: number; }</code>
265
+ <code>{ /** * Barcode formats to detect when barcodeDetection is enabled. */ formats?: BarcodeFormat[]; /** * Camera direction to use. Defaults to 'BACK'. */ cameraDirection?: 'BACK' | 'FRONT'; /** * Enable barcode detection on start. Defaults to false. * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events. */ barcodeDetection?: boolean; /** * Whether to show the highlight overlay for detected barcodes. Defaults to false. * Only applies when barcodeDetection is true. */ barcodeHighlight?: boolean; /** * Enable object detection (business card) on start. Defaults to false. * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events. */ objectDetection?: boolean; /** * Whether to show the highlight overlay for detected business cards. Defaults to true. * Only applies when objectDetection is true. */ objectHighlight?: boolean; /** * Minimum interval in seconds between consecutive 'rectangleDetected' events. * Only applies when objectDetection is true. * Defaults to 0 (no throttling beyond initial stability check). */ rectangleEmitIntervalSeconds?: number; /** * Optional regex pattern to filter scanned barcodes. * If provided, only barcodes matching this pattern will be reported. */ regex?: string; /** * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline). */ regexFlags?: string; /** * Cooldown in seconds after any detection event when both barcode and object detection are enabled. * After a barcode or rectangle is emitted, ALL detection pauses for this duration. * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value. * Only applies when both barcodeDetection and objectDetection are true. * Defaults to 0 (no throttle, both detections run simultaneously). */ detectionThrottleSeconds?: number; }</code>
266
266
 
267
267
 
268
268
  #### CapturePhotoOptions
@@ -92,7 +92,7 @@ public class CapacitorScannerPlugin extends Plugin {
92
92
  private ProcessCameraProvider cameraProvider;
93
93
  private BarcodeScanner scanner;
94
94
  private RectangleDetector rectangleDetector;
95
- private final TTLMap<String, VoteStatus> scannedCodesVotes = new TTLMap<>(5000); // 5 second TTL
95
+ private TTLMap<String, VoteStatus> scannedCodesVotes = new TTLMap<>(5000); // 5 second TTL
96
96
  private final int voteThreshold = 2;
97
97
  private final Executor executor = Executors.newSingleThreadExecutor();
98
98
  private final AtomicBoolean isScanning = new AtomicBoolean(false);
@@ -143,9 +143,10 @@ public class CapacitorScannerPlugin extends Plugin {
143
143
  // Configurable interval between rectangle detection event emissions (in milliseconds)
144
144
  private long rectangleEmitIntervalMs = 0; // Default: 0 (no throttling)
145
145
 
146
- // Detection throttle: pause rectangle detection after a successful barcode scan
146
+ // Detection throttle: pause ALL detection after any emit (when both types enabled)
147
147
  private long detectionThrottleMs = 0; // Default: 0 (no throttle)
148
- private long lastSuccessfulBarcodeScanTime = 0;
148
+ private long lastDetectionEmitTime = 0;
149
+ private boolean bothDetectionsEnabled = false;
149
150
 
150
151
  /**
151
152
  * Calculates the processing interval in milliseconds based on the desired
@@ -213,39 +214,48 @@ public class CapacitorScannerPlugin extends Plugin {
213
214
  }
214
215
  }
215
216
 
217
+ // Read detection throttle
218
+ if (call.getData().has("detectionThrottleSeconds")) {
219
+ try {
220
+ double throttleSeconds = call.getData().getDouble("detectionThrottleSeconds");
221
+ detectionThrottleMs = Math.max(0, (long) (throttleSeconds * 1000));
222
+ } catch (Exception e) {
223
+ detectionThrottleMs = 0;
224
+ }
225
+ } else {
226
+ detectionThrottleMs = 0;
227
+ }
228
+ lastDetectionEmitTime = 0;
229
+ bothDetectionsEnabled = enableBarcodeDetection && enableObjectDetection;
230
+
216
231
  // Enable object detection (businessCard) if requested
217
232
  if (enableObjectDetection) {
218
233
  enabledDetectionTypes.add("businessCard");
219
234
  if (enableObjectHighlight) {
220
235
  enabledHighlightTypes.add("businessCard");
221
236
  }
222
- // Read rectangleEmitIntervalSeconds if provided
237
+ // Read rectangleEmitIntervalSeconds: use 2X when both enabled with throttle
238
+ long emitInterval = 0;
223
239
  if (call.getData().has("rectangleEmitIntervalSeconds")) {
224
240
  try {
225
241
  double intervalSeconds = call.getData().getDouble("rectangleEmitIntervalSeconds");
226
- rectangleEmitIntervalMs = Math.max(0, (long) (intervalSeconds * 1000));
227
- echo("Using custom rectangleEmitIntervalSeconds: " + intervalSeconds + "s (" + rectangleEmitIntervalMs + "ms)");
242
+ emitInterval = Math.max(0, (long) (intervalSeconds * 1000));
228
243
  } catch (Exception e) {
229
- echo("Error parsing rectangleEmitIntervalSeconds: " + e.getMessage() + ". Using default 0ms");
230
- rectangleEmitIntervalMs = 0;
244
+ emitInterval = 0;
231
245
  }
232
- } else {
233
- rectangleEmitIntervalMs = 0;
234
246
  }
247
+ if (bothDetectionsEnabled && detectionThrottleMs > 0) {
248
+ emitInterval = Math.max(emitInterval, detectionThrottleMs * 2);
249
+ }
250
+ rectangleEmitIntervalMs = emitInterval;
235
251
  }
236
252
 
237
- // Read detection throttle
238
- if (call.getData().has("detectionThrottleSeconds")) {
239
- try {
240
- double throttleSeconds = call.getData().getDouble("detectionThrottleSeconds");
241
- detectionThrottleMs = Math.max(0, (long) (throttleSeconds * 1000));
242
- } catch (Exception e) {
243
- detectionThrottleMs = 0;
244
- }
253
+ // When both enabled with throttle, set barcode votes TTL to 2X
254
+ if (bothDetectionsEnabled && detectionThrottleMs > 0) {
255
+ scannedCodesVotes = new TTLMap<>(detectionThrottleMs * 2);
245
256
  } else {
246
- detectionThrottleMs = 0;
257
+ scannedCodesVotes = new TTLMap<>(5000);
247
258
  }
248
- lastSuccessfulBarcodeScanTime = 0;
249
259
 
250
260
  try {
251
261
  String cameraDirectionStr = call.getString("cameraDirection", "BACK");
@@ -481,23 +491,18 @@ public class CapacitorScannerPlugin extends Plugin {
481
491
  try {
482
492
  InputImage image = InputImage.fromMediaImage(mediaImage, rotationDegrees);
483
493
 
484
- boolean barcodeEnabled = scanner != null && enabledDetectionTypes.contains("barcode");
485
- boolean isRectangleThrottled = detectionThrottleMs > 0
486
- && lastSuccessfulBarcodeScanTime > 0
487
- && (System.currentTimeMillis() - lastSuccessfulBarcodeScanTime) < detectionThrottleMs;
494
+ // Check if detection is throttled (cooldown after any emit when both types enabled)
495
+ boolean isThrottled = bothDetectionsEnabled
496
+ && detectionThrottleMs > 0
497
+ && lastDetectionEmitTime > 0
498
+ && (System.currentTimeMillis() - lastDetectionEmitTime) < detectionThrottleMs;
499
+
500
+ boolean barcodeEnabled = scanner != null
501
+ && enabledDetectionTypes.contains("barcode")
502
+ && !isThrottled;
488
503
  boolean rectangleEnabled = rectangleDetector != null
489
504
  && enabledDetectionTypes.contains("businessCard")
490
- && !isRectangleThrottled;
491
-
492
- // Clear rectangle overlay when throttled
493
- if (isRectangleThrottled && enabledDetectionTypes.contains("businessCard")) {
494
- getActivity().runOnUiThread(() -> {
495
- synchronized (overlayLock) {
496
- currentRectangleRects.clear();
497
- updateOverlayDetections(currentBarcodeRects, currentRectangleRects);
498
- }
499
- });
500
- }
505
+ && !isThrottled;
501
506
 
502
507
  // Rectangle detection (synchronous) - runs first while imageProxy is still open
503
508
  if (rectangleEnabled) {
@@ -909,8 +914,8 @@ public class CapacitorScannerPlugin extends Plugin {
909
914
  data.put("format", BarcodeFormatHelper.barcodeFormatToString(format));
910
915
  notifyListeners("barcodeScanned", data, true);
911
916
 
912
- // Record successful scan time for detection throttle
913
- lastSuccessfulBarcodeScanTime = System.currentTimeMillis();
917
+ // Record emit time for detection throttle
918
+ lastDetectionEmitTime = System.currentTimeMillis();
914
919
 
915
920
  echo("Barcode " + rawValue + " scanned with format: "
916
921
  + BarcodeFormatHelper.barcodeFormatToString(format));
@@ -949,6 +954,8 @@ public class CapacitorScannerPlugin extends Plugin {
949
954
 
950
955
  // Update the last notification timestamp
951
956
  lastRectangleNotificationTime = currentTime;
957
+ // Record emit time for detection throttle
958
+ lastDetectionEmitTime = currentTime;
952
959
  } else {
953
960
  echo("Skipping notification - last one was less than 1 second ago");
954
961
  }
@@ -1187,7 +1194,9 @@ public class CapacitorScannerPlugin extends Plugin {
1187
1194
  lastRectangleDetectionTime = 0;
1188
1195
  rectangleEmitIntervalMs = 0;
1189
1196
  detectionThrottleMs = 0;
1190
- lastSuccessfulBarcodeScanTime = 0;
1197
+ lastDetectionEmitTime = 0;
1198
+ bothDetectionsEnabled = false;
1199
+ scannedCodesVotes = new TTLMap<>(5000);
1191
1200
 
1192
1201
  // Clear detection histories and caches
1193
1202
  enabledDetectionTypes.clear();
package/dist/docs.json CHANGED
@@ -344,7 +344,7 @@
344
344
  "docs": "",
345
345
  "types": [
346
346
  {
347
- "text": "{\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n /**\n * Duration in seconds to pause rectangle detection after a barcode is successfully scanned.\n * When both barcode and object detection are enabled, this gives barcode scanning priority\n * by temporarily suppressing rectangle detection after a barcode is emitted.\n * Defaults to 0 (no throttle, both detections run simultaneously).\n */\n detectionThrottleSeconds?: number;\n}",
347
+ "text": "{\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n /**\n * Cooldown in seconds after any detection event when both barcode and object detection are enabled.\n * After a barcode or rectangle is emitted, ALL detection pauses for this duration.\n * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value.\n * Only applies when both barcodeDetection and objectDetection are true.\n * Defaults to 0 (no throttle, both detections run simultaneously).\n */\n detectionThrottleSeconds?: number;\n}",
348
348
  "complexTypes": [
349
349
  "BarcodeFormat"
350
350
  ]
@@ -115,9 +115,10 @@ export declare type StartOptions = {
115
115
  */
116
116
  regexFlags?: string;
117
117
  /**
118
- * Duration in seconds to pause rectangle detection after a barcode is successfully scanned.
119
- * When both barcode and object detection are enabled, this gives barcode scanning priority
120
- * by temporarily suppressing rectangle detection after a barcode is emitted.
118
+ * Cooldown in seconds after any detection event when both barcode and object detection are enabled.
119
+ * After a barcode or rectangle is emitted, ALL detection pauses for this duration.
120
+ * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value.
121
+ * Only applies when both barcodeDetection and objectDetection are true.
121
122
  * Defaults to 0 (no throttle, both detections run simultaneously).
122
123
  */
123
124
  detectionThrottleSeconds?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAyKA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n /**\n * Start the camera preview with optional barcode and object detection.\n * @param options - Configuration options for the camera and detection\n */\n start(options?: StartOptions): Promise<void>;\n\n /**\n * Stop the camera preview and all detection.\n */\n stop(): Promise<void>;\n\n openSettings(): Promise<void>;\n\n capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;\n\n checkPermissions(): Promise<PermissionsResult>;\n\n requestPermissions(): Promise<PermissionsResult>;\n\n flipCamera(): Promise<void>;\n\n toggleFlash(): Promise<FlashResult>;\n\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n\n addListener(event: 'rectangleDetected', listenerFunc: (result: RectangleDetectedEvent) => void): Promise<void>;\n\n removeAllListeners(): Promise<void>;\n\n /**\n * Enable object detection (business card). This will start detecting business cards.\n * @param options - Optional configuration for object detection\n */\n enableObjectDetection(options?: ObjectDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable object detection. This will stop detecting business cards.\n */\n disableObjectDetection(): Promise<{ enabled: false }>;\n\n /**\n * Enable barcode detection. This will start detecting barcodes.\n * @param options - Optional configuration for barcode detection\n */\n enableBarcodeDetection(options?: BarcodeDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable barcode detection. This will stop detecting barcodes and clear cached barcodes.\n */\n disableBarcodeDetection(): Promise<{ enabled: false }>;\n\n /**\n * Set the camera zoom level.\n * @param options - The zoom options containing the level (1, 2, or 3)\n */\n zoom(options: ZoomOptions): Promise<ZoomResult>;\n}\n\nexport type BarcodeDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected barcodes.\n * Defaults to false.\n */\n showHighlight?: boolean;\n}\n\nexport type ObjectDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected business cards.\n * Defaults to true.\n */\n showHighlight?: boolean,\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * After an event is emitted, no new events will be emitted until this interval passes.\n * Set to 0 to emit events as soon as stability is detected.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n emitIntervalSeconds?: number\n}\n\nexport type StartOptions = {\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n /**\n * Duration in seconds to pause rectangle detection after a barcode is successfully scanned.\n * When both barcode and object detection are enabled, this gives barcode scanning priority\n * by temporarily suppressing rectangle detection after a barcode is emitted.\n * Defaults to 0 (no throttle, both detections run simultaneously).\n */\n detectionThrottleSeconds?: number;\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\n\nexport type RectangleDetectedEvent = {\n detected: true\n};\n\nexport type CapturePhotoOptions = {\n /**\n * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)\n * and 1.0 (highest quality, largest file size). Defaults to 1.0.\n * This parameter directly influences the compression level of the resulting JPEG image.\n */\n qualityRatio?: number;\n};\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport type FlashResult = { enabled: boolean };\n\nexport type ZoomOptions = {\n /**\n * The zoom level to set. Must be 1, 2, or 3.\n * - 1 = 1.0x (no zoom)\n * - 2 = 2.0x\n * - 3 = 3.0x\n */\n level: 1 | 2 | 3;\n};\n\nexport type ZoomResult = { level: number };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AA0KA,MAAM,CAAN,IAAY,aAYX;AAZD,WAAY,aAAa;IACvB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,qCAAoB,CAAA;IACpB,2CAA0B,CAAA;IAC1B,+BAAc,CAAA;IACd,iCAAgB,CAAA;IAChB,gCAAe,CAAA;IACf,mCAAkB,CAAA;IAClB,mCAAkB,CAAA;IAClB,+BAAc,CAAA;AAChB,CAAC,EAZW,aAAa,KAAb,aAAa,QAYxB","sourcesContent":["export interface CapacitorScannerPlugin {\n /**\n * Start the camera preview with optional barcode and object detection.\n * @param options - Configuration options for the camera and detection\n */\n start(options?: StartOptions): Promise<void>;\n\n /**\n * Stop the camera preview and all detection.\n */\n stop(): Promise<void>;\n\n openSettings(): Promise<void>;\n\n capturePhoto(options?: CapturePhotoOptions): Promise<CapturePhotoResult>;\n\n checkPermissions(): Promise<PermissionsResult>;\n\n requestPermissions(): Promise<PermissionsResult>;\n\n flipCamera(): Promise<void>;\n\n toggleFlash(): Promise<FlashResult>;\n\n addListener(event: 'barcodeScanned', listenerFunc: (result: BarcodeScannedEvent) => void): Promise<void>;\n\n addListener(event: 'rectangleDetected', listenerFunc: (result: RectangleDetectedEvent) => void): Promise<void>;\n\n removeAllListeners(): Promise<void>;\n\n /**\n * Enable object detection (business card). This will start detecting business cards.\n * @param options - Optional configuration for object detection\n */\n enableObjectDetection(options?: ObjectDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable object detection. This will stop detecting business cards.\n */\n disableObjectDetection(): Promise<{ enabled: false }>;\n\n /**\n * Enable barcode detection. This will start detecting barcodes.\n * @param options - Optional configuration for barcode detection\n */\n enableBarcodeDetection(options?: BarcodeDetectionOptions): Promise<{ enabled: true }>;\n\n /**\n * Disable barcode detection. This will stop detecting barcodes and clear cached barcodes.\n */\n disableBarcodeDetection(): Promise<{ enabled: false }>;\n\n /**\n * Set the camera zoom level.\n * @param options - The zoom options containing the level (1, 2, or 3)\n */\n zoom(options: ZoomOptions): Promise<ZoomResult>;\n}\n\nexport type BarcodeDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected barcodes.\n * Defaults to false.\n */\n showHighlight?: boolean;\n}\n\nexport type ObjectDetectionOptions = {\n /**\n * Whether to show the highlight overlay for detected business cards.\n * Defaults to true.\n */\n showHighlight?: boolean,\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * After an event is emitted, no new events will be emitted until this interval passes.\n * Set to 0 to emit events as soon as stability is detected.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n emitIntervalSeconds?: number\n}\n\nexport type StartOptions = {\n /**\n * Barcode formats to detect when barcodeDetection is enabled.\n */\n formats?: BarcodeFormat[];\n /**\n * Camera direction to use. Defaults to 'BACK'.\n */\n cameraDirection?: 'BACK' | 'FRONT';\n /**\n * Enable barcode detection on start. Defaults to false.\n * When enabled, the camera will automatically detect barcodes and emit 'barcodeScanned' events.\n */\n barcodeDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected barcodes. Defaults to false.\n * Only applies when barcodeDetection is true.\n */\n barcodeHighlight?: boolean;\n /**\n * Enable object detection (business card) on start. Defaults to false.\n * When enabled, the camera will automatically detect business cards and emit 'rectangleDetected' events.\n */\n objectDetection?: boolean;\n /**\n * Whether to show the highlight overlay for detected business cards. Defaults to true.\n * Only applies when objectDetection is true.\n */\n objectHighlight?: boolean;\n /**\n * Minimum interval in seconds between consecutive 'rectangleDetected' events.\n * Only applies when objectDetection is true.\n * Defaults to 0 (no throttling beyond initial stability check).\n */\n rectangleEmitIntervalSeconds?: number;\n /**\n * Optional regex pattern to filter scanned barcodes.\n * If provided, only barcodes matching this pattern will be reported.\n */\n regex?: string;\n /**\n * Optional regex flags (e.g., 'i' for case-insensitive, 'm' for multiline).\n */\n regexFlags?: string;\n /**\n * Cooldown in seconds after any detection event when both barcode and object detection are enabled.\n * After a barcode or rectangle is emitted, ALL detection pauses for this duration.\n * When set, barcode dedup TTL becomes 2x this value and rectangle emit interval becomes 2x this value.\n * Only applies when both barcodeDetection and objectDetection are true.\n * Defaults to 0 (no throttle, both detections run simultaneously).\n */\n detectionThrottleSeconds?: number;\n};\n\nexport type BarcodeScannedEvent = { scannedCode: string; format: string };\n\n\nexport type RectangleDetectedEvent = {\n detected: true\n};\n\nexport type CapturePhotoOptions = {\n /**\n * The desired quality of the captured image, expressed as a value between 0.0 (lowest quality, smallest file size)\n * and 1.0 (highest quality, largest file size). Defaults to 1.0.\n * This parameter directly influences the compression level of the resulting JPEG image.\n */\n qualityRatio?: number;\n};\n\nexport type PermissionsResult = { camera: 'prompt' | 'denied' | 'granted' };\n\nexport type CapturePhotoResult = { imageBase64: string };\n\nexport type FlashResult = { enabled: boolean };\n\nexport type ZoomOptions = {\n /**\n * The zoom level to set. Must be 1, 2, or 3.\n * - 1 = 1.0x (no zoom)\n * - 2 = 2.0x\n * - 3 = 3.0x\n */\n level: 1 | 2 | 3;\n};\n\nexport type ZoomResult = { level: number };\n\nexport enum BarcodeFormat {\n Aztec = 'AZTEC',\n Code39 = 'CODE_39',\n Code93 = 'CODE_93',\n Code128 = 'CODE_128',\n DataMatrix = 'DATA_MATRIX',\n Ean8 = 'EAN_8',\n Ean13 = 'EAN_13',\n Itf14 = 'ITF14',\n Pdf417 = 'PDF_417',\n QrCode = 'QR_CODE',\n UpcE = 'UPC_E',\n}\n\ndeclare global {\n interface PluginRegistry {\n QRScanner: CapacitorScannerPlugin;\n }\n}"]}
@@ -51,9 +51,10 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
51
51
  private var lastBarcodeDetectionTime: Date?
52
52
  private let barcodeVisibilityTimeout: TimeInterval = 1.0
53
53
 
54
- // Detection throttle: pause rectangle detection after a successful barcode scan
54
+ // Detection throttle: pause ALL detection after any emit (when both types enabled)
55
55
  private var detectionThrottleSeconds: TimeInterval = 0
56
- private var lastSuccessfulBarcodeScanTime: Date?
56
+ private var lastDetectionEmitTime: Date?
57
+ private var bothDetectionsEnabled: Bool = false
57
58
 
58
59
  // Detection state (controls what detection runs)
59
60
  private var enabledDetectionTypes: Set<String> = []
@@ -245,31 +246,24 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
245
246
  AVVideoCompressionPropertiesKey: [AVVideoQualityKey: qualityNumber]])
246
247
 
247
248
  let maxPrioritization = photoOutput.maxPhotoQualityPrioritization
248
- // Compare the raw values to determine which prioritization to use
249
- if maxPrioritization.rawValue >= AVCapturePhotoOutput.QualityPrioritization.balanced.rawValue {
250
- // Device supports .balanced prioritization
249
+ if maxPrioritization.rawValue >= AVCapturePhotoOutput.QualityPrioritization.quality.rawValue {
250
+ photoSettings.photoQualityPrioritization = .quality
251
+ } else if maxPrioritization.rawValue >= AVCapturePhotoOutput.QualityPrioritization.balanced.rawValue {
251
252
  photoSettings.photoQualityPrioritization = .balanced
252
253
  } else {
253
- // Use the lowest prioritization (.speed)
254
254
  photoSettings.photoQualityPrioritization = .speed
255
255
  }
256
256
 
257
+ // Enable high-resolution capture for sharper photos
258
+ photoSettings.isHighResolutionPhotoEnabled = true
259
+
257
260
  } else {
258
261
  print("JPEG codec not supported for photo capture. Using default settings.")
259
262
  // photoSettings remains the default initialized one
260
263
  }
261
264
 
262
265
  self.capturePhotoCall = call
263
-
264
- // If business card detection is enabled, wait for 1 second before capturing
265
- if self.enabledDetectionTypes.contains("businessCard") {
266
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
267
- photoOutput.capturePhoto(with: photoSettings, delegate: self)
268
- }
269
- } else {
270
- // Otherwise capture immediately
271
- photoOutput.capturePhoto(with: photoSettings, delegate: self)
272
- }
266
+ photoOutput.capturePhoto(with: photoSettings, delegate: self)
273
267
  }
274
268
 
275
269
  public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
@@ -296,37 +290,22 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
296
290
  if self.enabledDetectionTypes.contains("businessCard"),
297
291
  isRectangleCurrentlyDetected,
298
292
  let detectedRect = self.rectangleDetector?.lastDetectedRectangle {
299
- // If we have a snapshot from the detection, use it directly
300
- if let snapshot = detectedRect.snapshot {
301
- if let croppedImage = self.cropImage(snapshot, toRectangle: detectedRect.observation) {
302
- guard let croppedImageData = croppedImage.jpegData(compressionQuality: 0.9) else {
303
- self.capturePhotoCall?.reject("Unable to convert cropped image to data")
304
- return
305
- }
306
- let base64String = croppedImageData.base64EncodedString()
307
- self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
308
- } else {
309
- let base64String = imageData.base64EncodedString()
310
- self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
311
- }
312
- } else {
313
- // Use the captured photo if no snapshot available
314
- guard let image = UIImage(data: imageData) else {
315
- self.capturePhotoCall?.reject("Unable to create image from data")
316
- return
317
- }
293
+ // Always use the high-resolution captured photo for cropping (not the video frame snapshot)
294
+ guard let image = UIImage(data: imageData) else {
295
+ self.capturePhotoCall?.reject("Unable to create image from data")
296
+ return
297
+ }
318
298
 
319
- if let croppedImage = self.cropImage(image, toRectangle: detectedRect.observation) {
320
- guard let croppedImageData = croppedImage.jpegData(compressionQuality: 0.9) else {
321
- self.capturePhotoCall?.reject("Unable to convert cropped image to data")
322
- return
323
- }
324
- let base64String = croppedImageData.base64EncodedString()
325
- self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
326
- } else {
327
- let base64String = imageData.base64EncodedString()
328
- self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
299
+ if let croppedImage = self.cropImage(image, toRectangle: detectedRect) {
300
+ guard let croppedImageData = croppedImage.jpegData(compressionQuality: 0.9) else {
301
+ self.capturePhotoCall?.reject("Unable to convert cropped image to data")
302
+ return
329
303
  }
304
+ let base64String = croppedImageData.base64EncodedString()
305
+ self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
306
+ } else {
307
+ let base64String = imageData.base64EncodedString()
308
+ self.capturePhotoCall?.resolve(["imageBase64": "data:image/jpeg;base64, \(base64String)"])
330
309
  }
331
310
  } else {
332
311
  // No rectangle detected, return original image
@@ -451,6 +430,11 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
451
430
  }
452
431
  }
453
432
 
433
+ // Read detection throttle
434
+ self.detectionThrottleSeconds = call.getDouble("detectionThrottleSeconds") ?? 0
435
+ self.lastDetectionEmitTime = nil
436
+ self.bothDetectionsEnabled = enableBarcodeDetection && enableObjectDetection
437
+
454
438
  // Enable object detection (businessCard) if requested
455
439
  if enableObjectDetection {
456
440
  self.enabledDetectionTypes.insert("businessCard")
@@ -463,14 +447,18 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
463
447
  if enableObjectHighlight {
464
448
  self.rectangleDetector?.isHighlightEnabled = true
465
449
  }
466
- // Set emit interval if provided
467
- let emitInterval = call.getDouble("rectangleEmitIntervalSeconds") ?? 0
450
+ // Set emit interval: use 2X when both enabled with throttle, otherwise use provided value
451
+ var emitInterval = call.getDouble("rectangleEmitIntervalSeconds") ?? 0
452
+ if self.bothDetectionsEnabled && self.detectionThrottleSeconds > 0 {
453
+ emitInterval = max(emitInterval, self.detectionThrottleSeconds * 2)
454
+ }
468
455
  self.rectangleDetector?.setEmitInterval(emitInterval)
469
456
  }
470
457
 
471
- // Read detection throttle
472
- self.detectionThrottleSeconds = call.getDouble("detectionThrottleSeconds") ?? 0
473
- self.lastSuccessfulBarcodeScanTime = nil
458
+ // When both enabled with throttle, set barcode votes TTL to 2X
459
+ if self.bothDetectionsEnabled && self.detectionThrottleSeconds > 0 {
460
+ self.scannedCodesVotes = TTLMap<String, VoteStatus>(ttlSeconds: self.detectionThrottleSeconds * 2)
461
+ }
474
462
 
475
463
  // Handle Regex Filter
476
464
  if let pattern = call.getString("regex") {
@@ -530,6 +518,8 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
530
518
  self.photoOutput = photoOutput
531
519
  if captureSession.canAddOutput(photoOutput) {
532
520
  captureSession.addOutput(photoOutput)
521
+ photoOutput.maxPhotoQualityPrioritization = .quality
522
+ photoOutput.isHighResolutionCaptureEnabled = true
533
523
  } else {
534
524
  call.reject("Unable to add photo output to capture session")
535
525
  return
@@ -604,8 +594,10 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
604
594
  self.enabledDetectionTypes.removeAll()
605
595
  self.enabledHighlightTypes.removeAll()
606
596
  self.lastBarcodeDetectionTime = nil
607
- self.lastSuccessfulBarcodeScanTime = nil
597
+ self.lastDetectionEmitTime = nil
608
598
  self.detectionThrottleSeconds = 0
599
+ self.bothDetectionsEnabled = false
600
+ self.scannedCodesVotes = TTLMap<String, VoteStatus>(ttlSeconds: 5)
609
601
  self.removeOrientationChangeObserver()
610
602
  }
611
603
 
@@ -821,11 +813,14 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
821
813
  "format": CapacitorScannerHelpers.convertBarcodeScannerFormatToString(bestObservation.symbology),
822
814
  ])
823
815
 
824
- // Record successful scan time for detection throttle
825
- self.lastSuccessfulBarcodeScanTime = Date()
816
+ // Record emit time for detection throttle
817
+ self.lastDetectionEmitTime = Date()
826
818
 
827
- // Reset votes after successful scan
828
- self.scannedCodesVotes.clear()
819
+ // When both enabled: keep done flag for dedup (TTL handles expiry)
820
+ // When barcode only: clear to allow immediate re-voting
821
+ if !self.bothDetectionsEnabled {
822
+ self.scannedCodesVotes.clear()
823
+ }
829
824
  }
830
825
  }
831
826
 
@@ -842,6 +837,9 @@ public class CapacitorScannerPlugin: CAPPlugin, CAPBridgedPlugin, AVCaptureMetad
842
837
  "bottomLeft": ["x": corners.bottomLeft.x, "y": corners.bottomLeft.y],
843
838
  "bottomRight": ["x": corners.bottomRight.x, "y": corners.bottomRight.y],
844
839
  ])
840
+
841
+ // Record emit time for detection throttle
842
+ self.lastDetectionEmitTime = Date()
845
843
  }
846
844
 
847
845
  @objc func flipCamera(_ call: CAPPluginCall) {
@@ -1033,20 +1031,21 @@ extension CapacitorScannerPlugin: AVCaptureVideoDataOutputSampleBufferDelegate {
1033
1031
  // Prepare detection requests
1034
1032
  var requests: [VNRequest] = []
1035
1033
 
1036
- if self.enabledDetectionTypes.contains("barcode") {
1034
+ // Check if detection is throttled (cooldown after any emit when both types enabled)
1035
+ let isThrottled: Bool = {
1036
+ guard self.bothDetectionsEnabled,
1037
+ self.detectionThrottleSeconds > 0,
1038
+ let lastEmit = self.lastDetectionEmitTime else { return false }
1039
+ return Date().timeIntervalSince(lastEmit) < self.detectionThrottleSeconds
1040
+ }()
1041
+
1042
+ if !isThrottled, self.enabledDetectionTypes.contains("barcode") {
1037
1043
  requests.append(self.barcodeDetectionRequest)
1038
1044
  }
1039
1045
 
1040
- let isRectangleThrottled: Bool = {
1041
- guard self.detectionThrottleSeconds > 0,
1042
- let lastScan = self.lastSuccessfulBarcodeScanTime else { return false }
1043
- return Date().timeIntervalSince(lastScan) < self.detectionThrottleSeconds
1044
- }()
1045
-
1046
- if !isRectangleThrottled,
1046
+ if !isThrottled,
1047
1047
  self.enabledDetectionTypes.contains("businessCard"),
1048
1048
  let detector = self.rectangleDetector {
1049
- detector.updatePixelBuffer(pixelBuffer)
1050
1049
  requests.append(detector.visionRequest)
1051
1050
  }
1052
1051
 
@@ -18,14 +18,6 @@ struct RectangleCorners {
18
18
  /// Handles rectangle/business card detection using Vision framework
19
19
  class RectangleDetector {
20
20
 
21
- // MARK: - Types
22
-
23
- /// Stores detected rectangle with optional snapshot for cropping
24
- struct DetectedRectangle {
25
- let observation: VNRectangleObservation
26
- let snapshot: UIImage?
27
- }
28
-
29
21
  // MARK: - Configuration
30
22
 
31
23
  private let confidenceThreshold: Float = 0.8
@@ -38,10 +30,8 @@ class RectangleDetector {
38
30
 
39
31
  // MARK: - State
40
32
 
41
- private(set) var lastDetectedRectangle: DetectedRectangle?
33
+ private(set) var lastDetectedRectangle: VNRectangleObservation?
42
34
  private(set) var lastDetectionTime: Date?
43
- private var currentPixelBuffer: CVPixelBuffer?
44
- private let ciContext = CIContext(options: nil)
45
35
 
46
36
  // Stability tracking
47
37
  private var previousCorners: RectangleCorners?
@@ -61,7 +51,7 @@ class RectangleDetector {
61
51
  let request = VNDetectRectanglesRequest { [weak self] request, error in
62
52
  self?.processDetection(request, error: error)
63
53
  }
64
- request.maximumObservations = 1
54
+ request.maximumObservations = 10
65
55
  request.minimumAspectRatio = 0.5
66
56
  request.maximumAspectRatio = 0.8
67
57
  request.minimumSize = 0.2
@@ -100,11 +90,6 @@ class RectangleDetector {
100
90
  emitIntervalSeconds = max(0, seconds)
101
91
  }
102
92
 
103
- /// Update the current pixel buffer for snapshot capture
104
- func updatePixelBuffer(_ pixelBuffer: CVPixelBuffer) {
105
- self.currentPixelBuffer = pixelBuffer
106
- }
107
-
108
93
  /// Check for detection timeout and update overlay
109
94
  func checkTimeout() {
110
95
  smoother?.checkTimeout()
@@ -120,7 +105,6 @@ class RectangleDetector {
120
105
  func reset() {
121
106
  lastDetectedRectangle = nil
122
107
  lastDetectionTime = nil
123
- currentPixelBuffer = nil
124
108
  previousCorners = nil
125
109
  stabilityStartTime = nil
126
110
  lastEmitTime = nil
@@ -149,22 +133,16 @@ class RectangleDetector {
149
133
  return
150
134
  }
151
135
 
152
- // Find the rectangle with highest confidence
153
- guard let bestCandidate = observations.max(by: { $0.confidence < $1.confidence }) else {
154
- // Don't reset stability here - let checkTimeout() handle the grace period
155
- return
156
- }
157
-
158
- // Check if all corners are inside the screen with buffer
159
- guard isInsideScreen(bestCandidate) else {
136
+ // Find the largest rectangle by area
137
+ guard let bestCandidate = observations
138
+ .filter({ $0.confidence >= confidenceThreshold && isInsideScreen($0) })
139
+ .max(by: { area(of: $0) < area(of: $1) }) else {
160
140
  // Don't reset stability here - let checkTimeout() handle the grace period
161
141
  return
162
142
  }
163
143
 
164
- // Store rectangle if confidence is high enough
165
- if bestCandidate.confidence >= confidenceThreshold {
166
- captureSnapshot(for: bestCandidate)
167
- }
144
+ // Store rectangle observation for cropping
145
+ lastDetectedRectangle = bestCandidate
168
146
 
169
147
  // Update detection time
170
148
  lastDetectionTime = Date()
@@ -265,6 +243,17 @@ class RectangleDetector {
265
243
  return sqrt(dx * dx + dy * dy)
266
244
  }
267
245
 
246
+ /// Approximate area of a rectangle observation using the shoelace formula (normalized coordinates)
247
+ private func area(of obs: VNRectangleObservation) -> CGFloat {
248
+ let pts = [obs.topLeft, obs.topRight, obs.bottomRight, obs.bottomLeft]
249
+ var sum: CGFloat = 0
250
+ for i in 0..<pts.count {
251
+ let j = (i + 1) % pts.count
252
+ sum += pts[i].x * pts[j].y - pts[j].x * pts[i].y
253
+ }
254
+ return abs(sum) / 2.0
255
+ }
256
+
268
257
  private func isInsideScreen(_ observation: VNRectangleObservation) -> Bool {
269
258
  let corners = [
270
259
  observation.topLeft,
@@ -282,34 +271,4 @@ class RectangleDetector {
282
271
  }
283
272
  }
284
273
 
285
- private func captureSnapshot(for observation: VNRectangleObservation) {
286
- guard let pixelBuffer = currentPixelBuffer else {
287
- lastDetectedRectangle = DetectedRectangle(observation: observation, snapshot: nil)
288
- return
289
- }
290
-
291
- let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
292
-
293
- guard let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) else {
294
- lastDetectedRectangle = DetectedRectangle(observation: observation, snapshot: nil)
295
- return
296
- }
297
-
298
- // Create UIImage with correct orientation
299
- let deviceOrientation = UIDevice.current.orientation
300
- let uiOrientation: UIImage.Orientation
301
- switch deviceOrientation {
302
- case .landscapeLeft:
303
- uiOrientation = .right
304
- case .landscapeRight:
305
- uiOrientation = .left
306
- case .portraitUpsideDown:
307
- uiOrientation = .down
308
- default:
309
- uiOrientation = .up
310
- }
311
-
312
- let snapshot = UIImage(cgImage: cgImage, scale: 1.0, orientation: uiOrientation)
313
- lastDetectedRectangle = DetectedRectangle(observation: observation, snapshot: snapshot)
314
- }
315
274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scr2em/capacitor-scanner",
3
- "version": "6.0.31",
3
+ "version": "6.0.34",
4
4
  "description": "scan codes",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -29,23 +29,6 @@
29
29
  "plugin",
30
30
  "native"
31
31
  ],
32
- "scripts": {
33
- "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
- "verify:ios": "xcodebuild -scheme CapacitorScanner -destination generic/platform=iOS",
35
- "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
- "verify:web": "npm run build",
37
- "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
- "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
- "eslint": "eslint . --ext ts",
40
- "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
41
- "swiftlint": "node-swiftlint",
42
- "docgen": "docgen --api CapacitorScannerPlugin --output-readme README.md --output-json dist/docs.json",
43
- "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
44
- "clean": "rimraf ./dist",
45
- "watch": "tsc --watch",
46
- "prepublishOnly": "npm run build",
47
- "prepare": "npm run build"
48
- },
49
32
  "devDependencies": {
50
33
  "@capacitor/android": "6.0.0",
51
34
  "@capacitor/cli": "6.0.0",
@@ -78,5 +61,20 @@
78
61
  "android": {
79
62
  "src": "android"
80
63
  }
64
+ },
65
+ "scripts": {
66
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
67
+ "verify:ios": "xcodebuild -scheme CapacitorScanner -destination generic/platform=iOS",
68
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
69
+ "verify:web": "npm run build",
70
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
71
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
72
+ "eslint": "eslint . --ext ts",
73
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
74
+ "swiftlint": "node-swiftlint",
75
+ "docgen": "docgen --api CapacitorScannerPlugin --output-readme README.md --output-json dist/docs.json",
76
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
77
+ "clean": "rimraf ./dist",
78
+ "watch": "tsc --watch"
81
79
  }
82
80
  }