diff --git a/.github/workflows/NativePipeline.yml b/.github/workflows/NativePipeline.yml index 93a72daf5..b53ac35ab 100644 --- a/.github/workflows/NativePipeline.yml +++ b/.github/workflows/NativePipeline.yml @@ -854,13 +854,20 @@ jobs: if: env.IOS_APP_CACHED != 'true' working-directory: native-template/ios run: pod install + - name: "Patch MLKit frameworks for arm64 simulator" + if: env.IOS_APP_CACHED != 'true' + # MLKit (via react-native-vision-camera-barcode-scanner) has no arm64-simulator slice; + # re-tag its arm64 device objects as simulator so the arm64 simulator build links. + run: node native-widgets/scripts/test/patch-mlkit-arm64-simulator.js native-template/ios/Pods - name: "Build iOS app" if: env.IOS_APP_CACHED != 'true' working-directory: native-template/ios # Build for the simulator's native arch (arm64 on Apple Silicon runners). # ONLY_ACTIVE_ARCH=YES already limits to the active arch; the old # VALID_ARCHS="i386 x86_64" forced dead/foreign archs and broke on macos-26. - run: xcodebuild -workspace NativeTemplate.xcworkspace -scheme nativeTemplate -configuration Debug -sdk iphonesimulator -derivedDataPath build ONLY_ACTIVE_ARCH=YES + # EXCLUDED_ARCHS="i386 x86_64" explicitly excludes x86_64 to prevent Xcode from + # building a universal binary when Pods are arm64-only. + run: xcodebuild -workspace NativeTemplate.xcworkspace -scheme nativeTemplate -configuration Debug -sdk iphonesimulator -derivedDataPath build ONLY_ACTIVE_ARCH=YES EXCLUDED_ARCHS="i386 x86_64" - name: "Save built iOS app" if: env.IOS_APP_CACHED != 'true' uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 diff --git a/configs/e2e/native_dependencies.json b/configs/e2e/native_dependencies.json index 2b68899f3..9c61f1253 100644 --- a/configs/e2e/native_dependencies.json +++ b/configs/e2e/native_dependencies.json @@ -9,7 +9,10 @@ "react-native-svg": "15.15.4", "react-native-video": "6.19.2", "@react-native-async-storage/async-storage": "2.2.0", - "react-native-vision-camera": "4.7.3", + "react-native-vision-camera": "5.2.3", + "react-native-vision-camera-barcode-scanner": "5.2.3", + "react-native-nitro-modules": "0.37.0", + "react-native-nitro-image": "0.15.2", "react-native-view-shot": "5.1.0", "react-native-blob-util": "0.24.7", "react-native-file-viewer-turbo": "0.7.4", diff --git a/packages/jsActions/mobile-resources-native/CHANGELOG.md b/packages/jsActions/mobile-resources-native/CHANGELOG.md index 914e2093c..a37a1c75a 100644 --- a/packages/jsActions/mobile-resources-native/CHANGELOG.md +++ b/packages/jsActions/mobile-resources-native/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fixed an issue where the `TakePicture` and `TakePictureAdvanced` actions failed to capture photos on Android. - Fixed an issue where the `TakePicture` and `TakePictureAdvanced` actions did not properly clean up temporary files. - Replaced @notifee/react-native with react-native-notify-kit library. +- Updated @react-native-firebase/messaging to 21.14.0. ### Added diff --git a/packages/jsActions/mobile-resources-native/package.json b/packages/jsActions/mobile-resources-native/package.json index 2a82bceca..239ebbb5d 100644 --- a/packages/jsActions/mobile-resources-native/package.json +++ b/packages/jsActions/mobile-resources-native/package.json @@ -30,7 +30,7 @@ "dependencies": { "@react-native-camera-roll/camera-roll": "7.10.2", "react-native-notify-kit": "10.4.7", - "@react-native-firebase/messaging": "20.1.0", + "@react-native-firebase/messaging": "21.14.0", "@sbaiahmed1/react-native-biometrics": "0.15.0", "@swan-io/react-native-browser": "1.0.1", "fbjs": "3.0.4", diff --git a/packages/jsActions/nanoflow-actions-native/CHANGELOG.md b/packages/jsActions/nanoflow-actions-native/CHANGELOG.md index 083405075..9561e279f 100644 --- a/packages/jsActions/nanoflow-actions-native/CHANGELOG.md +++ b/packages/jsActions/nanoflow-actions-native/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed - Migrated the geolocation actions from `@react-native-community/geolocation` to `react-native-nitro-geolocation`. +- Updated react-native-nitro-modules to 0.37.0. ## [7.2.0] Nanoflow Commons - 2026-7-3 diff --git a/packages/jsActions/nanoflow-actions-native/package.json b/packages/jsActions/nanoflow-actions-native/package.json index c5972fb75..a625b931b 100644 --- a/packages/jsActions/nanoflow-actions-native/package.json +++ b/packages/jsActions/nanoflow-actions-native/package.json @@ -30,7 +30,7 @@ "js-base64": "~3.7.2", "mendix-native": "https://github.com/mendix/mendix-native/releases/download/v0.6.0/mendix-native-v0.6.0.tgz", "react-native-nitro-geolocation": "1.4.3", - "react-native-nitro-modules": "0.36.1", + "react-native-nitro-modules": "0.37.0", "react-native-permissions": "5.5.1", "react-native-geocoder": "0.5.0" }, diff --git a/packages/pluggableWidgets/barcode-scanner-native/CHANGELOG.md b/packages/pluggableWidgets/barcode-scanner-native/CHANGELOG.md index 1ec105dc9..5fab01b27 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/CHANGELOG.md +++ b/packages/pluggableWidgets/barcode-scanner-native/CHANGELOG.md @@ -6,8 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -- We added support for QR of available remaining types including pdf417 in barcode scanner functinality. -- We fixed the barcode mask not visible issue and selection logic of QR code. +### BREAKING + +- We updated react-native-vision-camera from v4.7.3 to v5.2.3 and added react-native-vision-camera-barcode-scanner v5.2.3 for barcode scanning. This version requires Make It Native version 11.12.3 or above. Older versions of Make It Native cannot run this version of the widget. +- For Native Template builds on iOS with push notifications enabled, Native Template version 19.1.8 or above is required. + +### Added + +- We added a `scan delay` property that delays scanning by the specified number of seconds, giving the end user time to position the scanner over the code. + +### Fixed + +- We fixed barcodes with patterned backgrounds not being scanned correctly on Android. ## [4.2.2] - 2025-12-23 diff --git a/packages/pluggableWidgets/barcode-scanner-native/package.json b/packages/pluggableWidgets/barcode-scanner-native/package.json index 20b5cbf1a..f8c252327 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/package.json +++ b/packages/pluggableWidgets/barcode-scanner-native/package.json @@ -1,7 +1,7 @@ { "name": "barcode-scanner-native", "widgetName": "BarcodeScanner", - "version": "4.5.0", + "version": "4.6.0", "license": "Apache-2.0", "repository": { "type": "git", @@ -22,7 +22,10 @@ "@mendix/piw-native-utils-internal": "*", "@mendix/piw-utils-internal": "*", "react-native-svg": "15.15.4", - "react-native-vision-camera": "4.7.3" + "react-native-vision-camera": "5.2.3", + "react-native-vision-camera-barcode-scanner": "5.2.3", + "react-native-nitro-modules": "0.37.0", + "react-native-nitro-image": "0.15.2" }, "devDependencies": { "@mendix/pluggable-widgets-tools": "*" diff --git a/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.tsx b/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.tsx index a48767b4b..929349f93 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.tsx +++ b/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.tsx @@ -1,8 +1,13 @@ import { flattenStyles } from "@mendix/piw-native-utils-internal"; import { ValueStatus } from "mendix"; -import { ReactElement, useCallback, useMemo, useRef, useState } from "react"; +import { ReactElement, useCallback, useMemo, useRef, useState, useEffect } from "react"; import { View, LayoutChangeEvent, Platform } from "react-native"; -import { Camera, useCodeScanner, Code, useCameraDevice, CodeScannerFrame } from "react-native-vision-camera"; +import { Camera, useCameraDevice, useCameraPermission } from "react-native-vision-camera"; +import { + useBarcodeScannerOutput, + type Barcode, + type TargetBarcodeFormat +} from "react-native-vision-camera-barcode-scanner"; import BarcodeMask from "./components/BarcodeMask"; import { BarcodeScannerProps } from "../typings/BarcodeScannerProps"; @@ -11,6 +16,29 @@ import { executeAction } from "@mendix/piw-utils-internal"; export type Props = BarcodeScannerProps; +// ML Kit on Android can misread 1D barcodes (e.g. Code 39) on busy backgrounds for a single frame, +// so a value is only accepted after it is detected on this many consecutive reads. +const REQUIRED_CONSECUTIVE_READS_ANDROID = 3; +const SCAN_LOCK_DURATION_MS = 2000; + +const BARCODE_FORMATS: TargetBarcodeFormat[] = [ + "qr-code", + "aztec", + "codabar", + "code-39", + "code-93", + "code-128", + "data-matrix", + "ean-13", + "ean-8", + "upc-a", + "upc-e", + "pdf-417", + "itf" +]; + +type Size = { width: number; height: number }; + type CodePositionInfo = { isWithinMask: boolean; distanceToMaskCenterSquared: number; @@ -31,7 +59,7 @@ type OverlapInfo = { }; type CandidateCode = { - code: Code; + barcode: Barcode; isWithinMask: boolean; distanceToMaskCenterSquared: number; overlapArea: number; @@ -39,53 +67,62 @@ type CandidateCode = { }; /** - * Transforms barcode coordinates from camera sensor space to screen view space. - * Handles platform-specific coordinate systems (iOS sensor landscape vs Android ML Kit rotated) - * and applies appropriate scaling based on device orientation. + * Transforms barcode coordinates from scanner buffer space to screen view space. + * Handles platform-specific coordinate systems: + * - iOS: Barcode coordinates are in sensor's native landscape space, need 90° rotation for portrait + * - Android: ML Kit already rotates coordinates to match device orientation */ function transformCodeCoordinates( - codeFrame: { x: number; y: number; width: number; height: number }, - scanFrame: CodeScannerFrame, + boundingBox: Barcode["boundingBox"], + bufferResolution: Size, viewWidth: number, viewHeight: number ): TransformedCoordinates { - const { width: frameWidth, height: frameHeight } = scanFrame; + const frameWidth = bufferResolution.width; + const frameHeight = bufferResolution.height; const isPortrait = viewHeight > viewWidth; + const left = boundingBox.left; + const top = boundingBox.top; + const right = boundingBox.right; + const bottom = boundingBox.bottom; + const codeFrameWidth = right - left; + const codeFrameHeight = bottom - top; + let codeX: number; let codeY: number; let codeWidth: number; let codeHeight: number; if (isPortrait && Platform.OS === "ios") { - // iOS: code.frame coordinates are in the sensor's native landscape space, + // iOS: barcode coordinates are in the sensor's native landscape space, // so we need a 90° rotation to map to portrait view coordinates. const scaleX = viewWidth / frameHeight; const scaleY = viewHeight / frameWidth; - codeX = (frameHeight - codeFrame.y - codeFrame.height) * scaleX; - codeY = codeFrame.x * scaleY; - codeWidth = codeFrame.height * scaleX; - codeHeight = codeFrame.width * scaleY; + codeX = (frameHeight - top - codeFrameHeight) * scaleX; + codeY = left * scaleY; + codeWidth = codeFrameHeight * scaleX; + codeHeight = codeFrameWidth * scaleY; } else if (isPortrait && Platform.OS === "android") { - // Android: ML Kit already rotates code.frame coordinates to match device orientation, - // but CodeScannerFrame still reports sensor landscape dimensions (e.g. 1920x1080). + // Android: ML Kit already rotates barcode coordinates to match device orientation, + // but buffer resolution still reports sensor landscape dimensions (e.g. 1920x1080). // Scale using the shorter dimension for X and longer for Y. const scaleX = viewWidth / Math.min(frameWidth, frameHeight); const scaleY = viewHeight / Math.max(frameWidth, frameHeight); - codeX = codeFrame.x * scaleX; - codeY = codeFrame.y * scaleY; - codeWidth = codeFrame.width * scaleX; - codeHeight = codeFrame.height * scaleY; + codeX = left * scaleX; + codeY = top * scaleY; + codeWidth = codeFrameWidth * scaleX; + codeHeight = codeFrameHeight * scaleY; } else { const scaleX = viewWidth / frameWidth; const scaleY = viewHeight / frameHeight; - codeX = codeFrame.x * scaleX; - codeY = codeFrame.y * scaleY; - codeWidth = codeFrame.width * scaleX; - codeHeight = codeFrame.height * scaleY; + codeX = left * scaleX; + codeY = top * scaleY; + codeWidth = codeFrameWidth * scaleX; + codeHeight = codeFrameHeight * scaleY; } return { codeX, codeY, codeWidth, codeHeight }; @@ -138,19 +175,53 @@ function compareCodesByPriority(a: CandidateCode, b: CandidateCode): number { export function BarcodeScanner(props: Props): ReactElement { const device = useCameraDevice("back"); + const [cameraViewDimensions, setCameraViewDimensions] = useState(null); + + const { hasPermission, canRequestPermission, requestPermission } = useCameraPermission(); + + // VisionCamera v5 no longer prompts implicitly when the session starts; it throws without permission + useEffect(() => { + if (canRequestPermission) { + requestPermission(); + } + }, [canRequestPermission, requestPermission]); const styles = useMemo(() => flattenStyles(defaultBarcodeScannerStyle, props.style), [props.style]); // Ref to track the lock state const isLockedRef = useRef(false); - - const [cameraViewDimensions, setCameraViewDimensions] = useState<{ width: number; height: number } | null>(null); + // Timer that releases the lock, kept so it can be cleared when the widget unmounts + const lockTimeoutRef = useRef | null>(null); + + // Time from which detected codes may be accepted, null until the camera is started + const scanStartTimeRef = useRef(null); + + // Last detected value and how many consecutive reads returned it + const lastReadValueRef = useRef(null); + const consecutiveReadsRef = useRef(0); + + // Starts a new scan: codes are accepted again after the configured scan delay, + // and the consecutive read count starts from zero + const resetScanState = useCallback(() => { + scanStartTimeRef.current = Date.now() + props.scanDelay * 1000; + lastReadValueRef.current = null; + consecutiveReadsRef.current = 0; + }, [props.scanDelay]); + + // Clear a pending lock timer when the widget unmounts + useEffect(() => { + return () => { + if (lockTimeoutRef.current) { + clearTimeout(lockTimeoutRef.current); + } + }; + }, []); const maskWidth = styles.mask.width || 280; const maskHeight = styles.mask.height || 260; const getCodePositionInfo = useCallback( - (code: Code, scanFrame: CodeScannerFrame): CodePositionInfo => { + (barcode: Barcode, bufferResolution: Size | undefined): CodePositionInfo => { if (!props.showMask) { return { isWithinMask: true, @@ -160,7 +231,7 @@ export function BarcodeScanner(props: Props): ReactElement { }; } - if (!cameraViewDimensions || !code.frame) { + if (!cameraViewDimensions || !bufferResolution) { return { isWithinMask: false, distanceToMaskCenterSquared: Number.MAX_SAFE_INTEGER, @@ -177,10 +248,9 @@ export function BarcodeScanner(props: Props): ReactElement { const maskCenterX = maskX + maskWidth / 2; const maskCenterY = maskY + maskHeight / 2; - // Transform Qr barcode coordinates from camera sensor space to view space const { codeX, codeY, codeWidth, codeHeight } = transformCodeCoordinates( - code.frame, - scanFrame, + barcode.boundingBox, + bufferResolution, viewWidth, viewHeight ); @@ -218,38 +288,58 @@ export function BarcodeScanner(props: Props): ReactElement { [props.showMask, cameraViewDimensions, maskWidth, maskHeight] ); - const onCodeScanned = useCallback( - (codes: Code[], frame: CodeScannerFrame) => { - // Block if still in cooldown - if (isLockedRef.current) { + // Read inside the scan callback to get the buffer resolution the scanner is actually using + const scannerOutputRef = useRef | null>(null); + + const onBarcodeScanned = useCallback( + (barcodes: Barcode[]) => { + // Block if still in cooldown or the previous detection is still being handled + if (isLockedRef.current || props.onDetect?.isExecuting) { return; } - if (props.barcode.status !== ValueStatus.Available || codes.length === 0) { + // Block until the camera is started and the scan delay has passed + if (scanStartTimeRef.current === null || Date.now() < scanStartTimeRef.current) { return; } - const candidates = codes - .map(code => { - const positionInfo = getCodePositionInfo(code, frame); - return { - code, - ...positionInfo - }; - }) + if (props.barcode.status !== ValueStatus.Available || barcodes.length === 0) { + return; + } + + const bufferResolution = scannerOutputRef.current?.currentResolution; + + const candidates = barcodes + .map(barcode => ({ + barcode, + ...getCodePositionInfo(barcode, bufferResolution) + })) .filter(item => item.isWithinMask); if (candidates.length === 0) { return; } - const selectedCode = candidates.sort(compareCodesByPriority)[0].code; + const value = candidates.sort(compareCodesByPriority)[0].barcode.rawValue; - if (!selectedCode.value) { + if (!value) { return; } - const { value } = selectedCode; + // Android only: accept the value after it was read on several consecutive reads, + // a different value restarts the count + if (Platform.OS === "android") { + if (value === lastReadValueRef.current) { + consecutiveReadsRef.current += 1; + } else { + lastReadValueRef.current = value; + consecutiveReadsRef.current = 1; + } + + if (consecutiveReadsRef.current < REQUIRED_CONSECUTIVE_READS_ANDROID) { + return; + } + } if (value !== props.barcode.value) { props.barcode.setValue(value); @@ -259,31 +349,32 @@ export function BarcodeScanner(props: Props): ReactElement { // Lock further scans for 2 seconds isLockedRef.current = true; - setTimeout(() => { + lockTimeoutRef.current = setTimeout(() => { isLockedRef.current = false; - }, 2000); + }, SCAN_LOCK_DURATION_MS); }, [props.barcode, props.onDetect, getCodePositionInfo] ); - const codeScanner = useCodeScanner({ - codeTypes: [ - "qr", - "aztec", - "codabar", - "code-39", - "code-93", - "code-128", - "data-matrix", - "ean-13", - "ean-8", - "upc-a", - "upc-e", - "pdf-417", - "itf" - ], - onCodeScanned + const onScanError = useCallback((error: Error) => { + console.warn("Barcode scanner error:", error); + }, []); + + const scannerOutput = useBarcodeScannerOutput({ + barcodeFormats: BARCODE_FORMATS, + // Full resolution so dense/patterned QR codes can be decoded + outputResolution: "full", + onBarcodeScanned, + onError: onScanError }); + useEffect(() => { + scannerOutputRef.current = scannerOutput; + }, [scannerOutput]); + + // The camera is ready, start the scan delay from now + const handleCameraStarted = useCallback(() => { + resetScanState(); + }, [resetScanState]); const handleCameraLayout = useCallback((event: LayoutChangeEvent) => { const { width, height } = event.nativeEvent.layout; @@ -291,16 +382,15 @@ export function BarcodeScanner(props: Props): ReactElement { }, []); return ( - - {device && ( + + {device && hasPermission && ( <> {props.showMask && ( diff --git a/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.xml b/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.xml index b8f87cf56..4850f262d 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.xml +++ b/packages/pluggableWidgets/barcode-scanner-native/src/BarcodeScanner.xml @@ -1,4 +1,4 @@ - + Barcode scanner Scan barcode and QR code values. @@ -11,7 +11,7 @@ Barcode The attribute that will receive the scanned barcode value. - + @@ -25,14 +25,20 @@ + + + Scan delay + Number of seconds to wait after the camera starts before barcodes are scanned, so the user has time to position the barcode. Set to 0 to start scanning immediately. + + On detect - + - + diff --git a/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/BarcodeScanner.spec.tsx b/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/BarcodeScanner.spec.tsx index 256765ee9..a4c1d2655 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/BarcodeScanner.spec.tsx +++ b/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/BarcodeScanner.spec.tsx @@ -5,14 +5,28 @@ import { View } from "react-native"; import { BarcodeScanner, Props } from "../BarcodeScanner"; import { defaultBarcodeScannerStyle } from "../ui/styles"; -let mockOnCodeScanned: ((codes: Array<{ value: string }>) => void) | undefined; +let mockOnBarcodeScanned: ((barcodes: Array<{ rawValue: string }>) => void) | undefined; jest.mock("react-native-vision-camera", () => ({ - Camera: ({ children, ...props }: any) => {children}, + Camera: ({ children, onStarted, ...props }: any) => { + // Call onStarted to simulate camera being ready + if (onStarted) { + setTimeout(() => onStarted(), 0); + } + return {children}; + }, useCameraDevice: () => "mock-device", - useCodeScanner: (options: any) => { - mockOnCodeScanned = options.onCodeScanned; - return "mockCodeScanner"; + useCameraPermission: () => ({ + hasPermission: true, + canRequestPermission: false, + requestPermission: jest.fn() + }) +})); + +jest.mock("react-native-vision-camera-barcode-scanner", () => ({ + useBarcodeScannerOutput: (options: any) => { + mockOnBarcodeScanned = options.onBarcodeScanned; + return "mockScannerOutput"; } })); @@ -28,7 +42,8 @@ describe("BarcodeScanner", () => { showMask: false, name: "barcode-scanner-test", style: [], - barcode: new EditableValueBuilder().build() + barcode: new EditableValueBuilder().build(), + scanDelay: 0 }; }); @@ -55,15 +70,22 @@ describe("BarcodeScanner", () => { const onDetectAction = actionValue(); render(); - // Simulate scanning - mockOnCodeScanned?.([{ value: "value" }]); + // Wait for camera start and advance time past scan delay + jest.runOnlyPendingTimers(); + + // Simulate scanning (3 consecutive reads required on Android) + mockOnBarcodeScanned?.([{ rawValue: "value" }]); + mockOnBarcodeScanned?.([{ rawValue: "value" }]); + mockOnBarcodeScanned?.([{ rawValue: "value" }]); jest.advanceTimersByTime(2000); expect(defaultProps.barcode.setValue).toHaveBeenCalledWith("value"); expect(onDetectAction.execute).toHaveBeenCalledTimes(1); // Another scan - mockOnCodeScanned?.([{ value: "value1" }]); + mockOnBarcodeScanned?.([{ rawValue: "value1" }]); + mockOnBarcodeScanned?.([{ rawValue: "value1" }]); + mockOnBarcodeScanned?.([{ rawValue: "value1" }]); jest.advanceTimersByTime(2000); expect(defaultProps.barcode.setValue).toHaveBeenCalledWith("value1"); diff --git a/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/__snapshots__/BarcodeScanner.spec.tsx.snap b/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/__snapshots__/BarcodeScanner.spec.tsx.snap index 6fcf797a8..ff064b8db 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/__snapshots__/BarcodeScanner.spec.tsx.snap +++ b/packages/pluggableWidgets/barcode-scanner-native/src/__tests__/__snapshots__/BarcodeScanner.spec.tsx.snap @@ -9,13 +9,17 @@ exports[`BarcodeScanner renders 1`] = ` "minHeight": 100, } } + testID="barcode-scanner-test" > `; @@ -37,13 +40,17 @@ exports[`BarcodeScanner renders with mask 1`] = ` "minHeight": 100, } } + testID="barcode-scanner-test" > - + diff --git a/packages/pluggableWidgets/barcode-scanner-native/typings/BarcodeScannerProps.d.ts b/packages/pluggableWidgets/barcode-scanner-native/typings/BarcodeScannerProps.d.ts index d303b6d4b..a961fea4d 100644 --- a/packages/pluggableWidgets/barcode-scanner-native/typings/BarcodeScannerProps.d.ts +++ b/packages/pluggableWidgets/barcode-scanner-native/typings/BarcodeScannerProps.d.ts @@ -12,6 +12,7 @@ export interface BarcodeScannerProps