Skip to content

Commit b7aa244

Browse files
authored
Moo 1852/update camera lib v2 (#347)
2 parents 443c139 + bfaaa0d commit b7aa244

File tree

11 files changed

+146
-170
lines changed

11 files changed

+146
-170
lines changed

configs/e2e/native_dependencies.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"react-native-svg": "15.11.1",
1010
"react-native-video": "6.10.0",
1111
"@react-native-async-storage/async-storage": "2.0.0",
12-
"react-native-camera": "3.40.0",
12+
"react-native-vision-camera": "4.7.1",
1313
"react-native-view-shot": "4.0.3",
1414
"react-native-blob-util": "0.21.2",
1515
"react-native-file-viewer-turbo": "0.6.0",

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
"@mendix/[email protected]": "patches/@mendix+pluggable-widgets-tools+10.21.1.patch",
9797
"@ptomasroos/[email protected]": "patches/@ptomasroos+react-native-multi-slider+1.0.0.patch",
9898
"[email protected]": "patches/react-native-action-button+2.8.5.patch",
99-
"[email protected]": "patches/react-native-camera+3.40.0.patch",
10099
"[email protected]": "patches/react-native-gesture-handler+2.24.0.patch",
101100
"[email protected]": "patches/react-native-slider+0.11.0.patch",
102101
"[email protected]": "patches/react-native-snap-carousel+3.9.1.patch"

packages/pluggableWidgets/barcode-scanner-native/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
- We migrated to react-native-vision-camera from react-native-camera.
10+
911
## [4.1.0] - 2024-12-3
1012

1113
### Changed

packages/pluggableWidgets/barcode-scanner-native/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "barcode-scanner-native",
33
"widgetName": "BarcodeScanner",
4-
"version": "4.1.0",
4+
"version": "4.2.0",
55
"license": "Apache-2.0",
66
"repository": {
77
"type": "git",
@@ -21,9 +21,8 @@
2121
"dependencies": {
2222
"@mendix/piw-native-utils-internal": "*",
2323
"@mendix/piw-utils-internal": "*",
24-
"deprecated-react-native-prop-types": "^4.0.0",
2524
"react-native-barcode-mask": "^1.2.4",
26-
"react-native-camera": "3.40.0"
25+
"react-native-vision-camera": "4.7.1"
2726
},
2827
"devDependencies": {
2928
"@mendix/pluggable-widgets-tools": "*"
Lines changed: 60 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { flattenStyles } from "@mendix/piw-native-utils-internal";
22
import { ValueStatus } from "mendix";
3-
import { Component, createElement } from "react";
3+
import { createElement, ReactElement, useCallback, useMemo, useRef } from "react";
44
import { View } from "react-native";
5-
import { RNCamera } from "react-native-camera";
5+
import { Camera, useCodeScanner, Code, useCameraDevice } from "react-native-vision-camera";
66
import BarcodeMask from "react-native-barcode-mask";
77

88
import { BarcodeScannerProps } from "../typings/BarcodeScannerProps";
@@ -11,55 +11,68 @@ import { executeAction } from "@mendix/piw-utils-internal";
1111

1212
export type Props = BarcodeScannerProps<BarcodeScannerStyle>;
1313

14-
export class BarcodeScanner extends Component<Props> {
15-
private readonly styles = flattenStyles(defaultBarcodeScannerStyle, this.props.style);
16-
private readonly onBarCodeReadHandler = throttle(this.onBarCodeRead.bind(this), 2000);
14+
export function BarcodeScanner(props: Props): ReactElement {
15+
const device = useCameraDevice("back");
1716

18-
render(): JSX.Element {
19-
return (
20-
<View style={this.styles.container}>
21-
<RNCamera
22-
testID={this.props.name}
23-
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
24-
captureAudio={false}
25-
onBarCodeRead={this.onBarCodeReadHandler}
26-
>
27-
{this.props.showMask && (
28-
<BarcodeMask
29-
edgeColor={this.styles.mask.color}
30-
width={this.styles.mask.width}
31-
height={this.styles.mask.height}
32-
backgroundColor={this.styles.mask.backgroundColor}
33-
showAnimatedLine={this.props.showAnimatedLine}
34-
/>
35-
)}
36-
</RNCamera>
37-
</View>
38-
);
39-
}
17+
const styles = useMemo(() => flattenStyles(defaultBarcodeScannerStyle, props.style), [props.style]);
4018

41-
private onBarCodeRead(event: { data: string }): void {
42-
if (this.props.barcode.status !== ValueStatus.Available || !event.data) {
43-
return;
44-
}
19+
// Ref to track the lock state
20+
const isLockedRef = useRef(false);
4521

46-
if (event.data !== this.props.barcode.value) {
47-
this.props.barcode.setValue(event.data);
48-
}
22+
const onCodeScanned = useCallback(
23+
(codes: Code[]) => {
24+
// Block if still in cooldown
25+
if (isLockedRef.current) {
26+
return;
27+
}
4928

50-
executeAction(this.props.onDetect);
51-
}
52-
}
29+
if (props.barcode.status !== ValueStatus.Available || codes.length === 0 || !codes[0].value) {
30+
return;
31+
}
32+
33+
const { value } = codes[0];
34+
if (value !== props.barcode.value) {
35+
props.barcode.setValue(value);
36+
}
37+
38+
executeAction(props.onDetect);
5339

54-
export function throttle<F extends (...params: any[]) => void>(fn: F, threshold: number): F {
55-
let wait = false;
56-
return function invokeFn(this: any, ...args: any[]) {
57-
if (!wait) {
58-
fn(...args);
59-
wait = true;
40+
// Lock further scans for 2 seconds
41+
isLockedRef.current = true;
6042
setTimeout(() => {
61-
wait = false;
62-
}, threshold);
63-
}
64-
} as F;
43+
isLockedRef.current = false;
44+
}, 2000);
45+
},
46+
[props.barcode, props.onDetect]
47+
);
48+
49+
const codeScanner = useCodeScanner({
50+
codeTypes: ["ean-13", "qr", "aztec", "codabar", "code-128", "data-matrix"],
51+
onCodeScanned
52+
});
53+
54+
return (
55+
<View style={styles.container}>
56+
{device && (
57+
<Camera
58+
testID={props.name}
59+
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
60+
audio={false}
61+
isActive
62+
device={device}
63+
codeScanner={codeScanner}
64+
>
65+
{props.showMask && (
66+
<BarcodeMask
67+
edgeColor={styles.mask.color}
68+
width={styles.mask.width}
69+
height={styles.mask.height}
70+
backgroundColor={styles.mask.backgroundColor}
71+
showAnimatedLine={props.showAnimatedLine}
72+
/>
73+
)}
74+
</Camera>
75+
)}
76+
</View>
77+
);
6578
}
Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
// __tests__/BarcodeScanner.spec.tsx
12
import { actionValue, EditableValueBuilder } from "@mendix/piw-utils-internal";
3+
import { render } from "@testing-library/react-native";
24
import { createElement } from "react";
3-
import { fireEvent, render, RenderAPI } from "@testing-library/react-native";
5+
import { View } from "react-native";
6+
import { BarcodeScanner, Props } from "../BarcodeScanner";
47

5-
import { BarcodeScanner, Props, throttle } from "../BarcodeScanner";
6-
import { RNCamera } from "./__mocks__/RNCamera";
8+
let mockOnCodeScanned: ((codes: Array<{ value: string }>) => void) | undefined;
79

8-
jest.mock("react-native-camera", () => jest.requireActual("./__mocks__/RNCamera"));
10+
jest.mock("react-native-vision-camera", () => ({
11+
Camera: ({ children, ...props }: any) => <View {...props}>{children}</View>,
12+
useCameraDevice: () => "mock-device",
13+
useCodeScanner: (options: any) => {
14+
mockOnCodeScanned = options.onCodeScanned;
15+
return "mockCodeScanner";
16+
}
17+
}));
918

10-
jest.useFakeTimers();
19+
jest.mock("react-native-barcode-mask", () => "BarcodeMask");
1120

1221
describe("BarcodeScanner", () => {
1322
let defaultProps: Props;
1423

1524
beforeEach(() => {
25+
jest.useFakeTimers();
1626
defaultProps = {
1727
showAnimatedLine: false,
1828
showMask: false,
@@ -22,84 +32,41 @@ describe("BarcodeScanner", () => {
2232
};
2333
});
2434

35+
afterEach(() => {
36+
jest.clearAllTimers();
37+
});
38+
2539
it("renders", () => {
2640
const component = render(<BarcodeScanner {...defaultProps} />);
27-
2841
expect(component.toJSON()).toMatchSnapshot();
2942
});
3043

3144
it("renders with mask", () => {
3245
const component = render(<BarcodeScanner {...defaultProps} showMask />);
33-
3446
expect(component.toJSON()).toMatchSnapshot();
3547
});
3648

3749
it("renders with mask with animated line", () => {
3850
const component = render(<BarcodeScanner {...defaultProps} showMask showAnimatedLine />);
39-
4051
expect(component.toJSON()).toMatchSnapshot();
4152
});
4253

43-
it("sets a value and executes the on detect action when a new barcode is scanned", async () => {
54+
it("sets a value and executes the onDetect action when a new barcode is scanned", () => {
4455
const onDetectAction = actionValue();
45-
const component = render(<BarcodeScanner {...defaultProps} onDetect={onDetectAction} />);
56+
render(<BarcodeScanner {...defaultProps} onDetect={onDetectAction} />);
4657

47-
detectBarcode(component, "value");
58+
// Simulate scanning
59+
mockOnCodeScanned?.([{ value: "value" }]);
4860
jest.advanceTimersByTime(2000);
4961

5062
expect(defaultProps.barcode.setValue).toHaveBeenCalledWith("value");
5163
expect(onDetectAction.execute).toHaveBeenCalledTimes(1);
5264

53-
detectBarcode(component, "value1");
54-
jest.advanceTimersByTime(100);
55-
detectBarcode(component, "value2");
56-
// Events are not fired immediately by testing-library, so firing with 1999 will be already too late for the previous action
57-
jest.advanceTimersByTime(1800);
58-
detectBarcode(component, "value3");
59-
65+
// Another scan
66+
mockOnCodeScanned?.([{ value: "value1" }]);
6067
jest.advanceTimersByTime(2000);
6168

6269
expect(defaultProps.barcode.setValue).toHaveBeenCalledWith("value1");
6370
expect(onDetectAction.execute).toHaveBeenCalledTimes(2);
64-
65-
detectBarcode(component, "value2");
66-
detectBarcode(component, "value3");
67-
detectBarcode(component, "value4");
68-
69-
jest.advanceTimersByTime(2000);
70-
71-
expect(defaultProps.barcode.setValue).toHaveBeenCalledWith("value2");
72-
expect(onDetectAction.execute).toHaveBeenCalledTimes(3);
73-
});
74-
75-
describe("throttling", () => {
76-
const func: (...args: any) => void = jest.fn();
77-
const args = ["argument", { prop: "arguments" }];
78-
79-
it("should execute function in correct time intervals", () => {
80-
const throttleFunc = throttle(func, 100);
81-
82-
throttleFunc(...args);
83-
expect(func).toHaveBeenCalledTimes(1);
84-
jest.advanceTimersByTime(100);
85-
throttleFunc(...args);
86-
jest.advanceTimersByTime(99);
87-
throttleFunc(...args);
88-
throttleFunc(...args);
89-
expect(func).toHaveBeenCalledTimes(2);
90-
jest.advanceTimersByTime(100);
91-
expect(func).toHaveBeenCalledTimes(2);
92-
});
9371
});
9472
});
95-
96-
function detectBarcode(component: RenderAPI, barcode: string): void {
97-
fireEvent(component.UNSAFE_getByType(RNCamera), "barCodeRead", {
98-
data: barcode,
99-
type: "qr",
100-
bounds: [
101-
{ x: "", y: "" },
102-
{ x: "", y: "" }
103-
]
104-
});
105-
}

packages/pluggableWidgets/barcode-scanner-native/src/__tests__/__mocks__/RNCamera.tsx

Lines changed: 0 additions & 21 deletions
This file was deleted.

packages/pluggableWidgets/barcode-scanner-native/src/__tests__/__snapshots__/BarcodeScanner.spec.tsx.snap

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ exports[`BarcodeScanner renders 1`] = `
1111
}
1212
>
1313
<View
14+
audio={false}
15+
codeScanner="mockCodeScanner"
16+
device="mock-device"
17+
isActive={true}
1418
style={
1519
{
1620
"alignItems": "center",
1721
"flex": 1,
1822
"justifyContent": "center",
1923
}
2024
}
25+
testID="barcode-scanner-test"
2126
/>
2227
</View>
2328
`;
@@ -33,14 +38,25 @@ exports[`BarcodeScanner renders with mask 1`] = `
3338
}
3439
>
3540
<View
41+
audio={false}
42+
codeScanner="mockCodeScanner"
43+
device="mock-device"
44+
isActive={true}
3645
style={
3746
{
3847
"alignItems": "center",
3948
"flex": 1,
4049
"justifyContent": "center",
4150
}
4251
}
43-
/>
52+
testID="barcode-scanner-test"
53+
>
54+
<BarcodeMask
55+
backgroundColor="rgba(0, 0, 0, 0.6)"
56+
edgeColor="#62B1F6"
57+
showAnimatedLine={false}
58+
/>
59+
</View>
4460
</View>
4561
`;
4662

@@ -55,13 +71,24 @@ exports[`BarcodeScanner renders with mask with animated line 1`] = `
5571
}
5672
>
5773
<View
74+
audio={false}
75+
codeScanner="mockCodeScanner"
76+
device="mock-device"
77+
isActive={true}
5878
style={
5979
{
6080
"alignItems": "center",
6181
"flex": 1,
6282
"justifyContent": "center",
6383
}
6484
}
65-
/>
85+
testID="barcode-scanner-test"
86+
>
87+
<BarcodeMask
88+
backgroundColor="rgba(0, 0, 0, 0.6)"
89+
edgeColor="#62B1F6"
90+
showAnimatedLine={true}
91+
/>
92+
</View>
6693
</View>
6794
`;

0 commit comments

Comments
 (0)