@eohjsc/react-native-smart-city 0.7.34 → 0.7.36
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/android/build.gradle +1 -1
- package/package.json +6 -1
- package/src/screens/HanetCamera/CaptureFaceID.js +69 -40
- package/src/screens/HanetCamera/__test__/CaptureFaceID.test.js +31 -2
- package/src/screens/ScanChipQR/components/QRScan/__test__/QRScan.test.js +35 -8
- package/src/screens/ScanChipQR/components/QRScan/index.js +76 -59
package/android/build.gradle
CHANGED
|
@@ -39,7 +39,7 @@ buildscript {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
android {
|
|
42
|
-
namespace "com.
|
|
42
|
+
namespace "com.eoh.smartcity"
|
|
43
43
|
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
|
|
44
44
|
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
|
|
45
45
|
defaultConfig {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eohjsc/react-native-smart-city",
|
|
3
3
|
"title": "React Native Smart Home",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.36",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -203,6 +203,11 @@
|
|
|
203
203
|
"validator": "^13.1.1",
|
|
204
204
|
"victory-native": "^41.0.1"
|
|
205
205
|
},
|
|
206
|
+
"peerDependencies": {
|
|
207
|
+
"react": ">=18.0.0",
|
|
208
|
+
"react-native": ">=0.73.0",
|
|
209
|
+
"react-native-vision-camera": ">=4.0.0"
|
|
210
|
+
},
|
|
206
211
|
"bugs": {
|
|
207
212
|
"url": "https://github.com/github_account/react-native-smart-city/issues"
|
|
208
213
|
},
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import React, { memo, useState, useRef } from 'react';
|
|
2
|
-
import { View, ActivityIndicator, TouchableOpacity } from 'react-native';
|
|
1
|
+
import React, { memo, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { View, ActivityIndicator, TouchableOpacity, Image } from 'react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Camera,
|
|
6
|
+
useCameraDevice,
|
|
7
|
+
useCameraPermission,
|
|
8
|
+
} from 'react-native-vision-camera';
|
|
5
9
|
import { HeaderCustom } from '../../commons/Header';
|
|
6
10
|
import { FullLoading } from '../../commons';
|
|
7
11
|
import Text from '../../commons/Text';
|
|
@@ -30,37 +34,44 @@ const CaptureFaceID = ({ route }) => {
|
|
|
30
34
|
const [capturing, setCapturing] = useState(false);
|
|
31
35
|
const [imageUri, setImageUri] = useState();
|
|
32
36
|
const [loading, setLoading] = useState(false);
|
|
37
|
+
const [isCameraActive, setIsCameraActive] = useState(true);
|
|
33
38
|
|
|
34
|
-
const refCamera = useRef();
|
|
39
|
+
const refCamera = useRef(null);
|
|
40
|
+
const { hasPermission, requestPermission } = useCameraPermission();
|
|
41
|
+
const device = useCameraDevice('back');
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!hasPermission) {
|
|
45
|
+
requestPermission();
|
|
46
|
+
}
|
|
47
|
+
}, [hasPermission, requestPermission]);
|
|
35
48
|
|
|
36
49
|
const beginCaptureFace = async () => {
|
|
37
50
|
setCapturing(true);
|
|
38
51
|
setOpenCamera(true);
|
|
52
|
+
setIsCameraActive(true);
|
|
39
53
|
};
|
|
40
54
|
|
|
41
55
|
const captureFace = async () => {
|
|
42
56
|
if (refCamera.current) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
setImageUri(data.uri);
|
|
55
|
-
setCapturing(false);
|
|
57
|
+
try {
|
|
58
|
+
const photo = await refCamera.current.takePhoto({
|
|
59
|
+
qualityPrioritization: 'balanced',
|
|
60
|
+
});
|
|
61
|
+
const uri = `file://${photo.path}`;
|
|
62
|
+
setImageUri(uri);
|
|
63
|
+
setCapturing(false);
|
|
64
|
+
setIsCameraActive(false);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Failed to take photo:', error);
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
};
|
|
58
70
|
|
|
59
71
|
const resumeCapture = () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
72
|
+
setCapturing(true);
|
|
73
|
+
setImageUri(undefined);
|
|
74
|
+
setIsCameraActive(true);
|
|
64
75
|
};
|
|
65
76
|
|
|
66
77
|
const updateMemberFaceID = async () => {
|
|
@@ -109,30 +120,48 @@ const CaptureFaceID = ({ route }) => {
|
|
|
109
120
|
}
|
|
110
121
|
};
|
|
111
122
|
|
|
123
|
+
const renderCameraContent = () => {
|
|
124
|
+
if (!hasPermission || !device) {
|
|
125
|
+
return <LoadingCamera />;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<>
|
|
130
|
+
<Camera
|
|
131
|
+
ref={refCamera}
|
|
132
|
+
style={{
|
|
133
|
+
height: Constants.height,
|
|
134
|
+
width: Constants.width,
|
|
135
|
+
}}
|
|
136
|
+
device={device}
|
|
137
|
+
isActive={isCameraActive}
|
|
138
|
+
photo={true}
|
|
139
|
+
/>
|
|
140
|
+
{isCameraActive && (
|
|
141
|
+
<View style={styles.maskOuter}>
|
|
142
|
+
<FaceFrameSvg />
|
|
143
|
+
</View>
|
|
144
|
+
)}
|
|
145
|
+
{!isCameraActive && imageUri && (
|
|
146
|
+
<Image
|
|
147
|
+
source={{ uri: imageUri }}
|
|
148
|
+
style={{
|
|
149
|
+
position: 'absolute',
|
|
150
|
+
height: Constants.height,
|
|
151
|
+
width: Constants.width,
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
</>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
112
159
|
return (
|
|
113
160
|
<View style={styles.container}>
|
|
114
161
|
<HeaderCustom title={title} isShowSeparator />
|
|
115
162
|
{openCamera && (
|
|
116
163
|
<View style={styles.wrapCamera}>
|
|
117
|
-
|
|
118
|
-
ref={refCamera}
|
|
119
|
-
style={{
|
|
120
|
-
height: Constants.height,
|
|
121
|
-
width: Constants.width,
|
|
122
|
-
}}
|
|
123
|
-
type={RNCamera.Constants.Type.back}
|
|
124
|
-
>
|
|
125
|
-
{({ camera, status, recordAudioPermissionStatus }) => {
|
|
126
|
-
if (status !== 'READY') {
|
|
127
|
-
return <LoadingCamera />;
|
|
128
|
-
}
|
|
129
|
-
return (
|
|
130
|
-
<View style={styles.maskOuter}>
|
|
131
|
-
<FaceFrameSvg />
|
|
132
|
-
</View>
|
|
133
|
-
);
|
|
134
|
-
}}
|
|
135
|
-
</RNCamera>
|
|
164
|
+
{renderCameraContent()}
|
|
136
165
|
</View>
|
|
137
166
|
)}
|
|
138
167
|
{!openCamera && (
|
|
@@ -5,12 +5,40 @@ import MockAdapter from 'axios-mock-adapter';
|
|
|
5
5
|
import { SCProvider } from '../../../context';
|
|
6
6
|
import { mockSCStore } from '../../../context/mockStore';
|
|
7
7
|
import HanetCaptureFaceID from '../CaptureFaceID';
|
|
8
|
-
import {
|
|
8
|
+
import { Camera } from 'react-native-vision-camera';
|
|
9
9
|
import api from '../../../utils/Apis/axios';
|
|
10
10
|
import { API } from '../../../configs';
|
|
11
11
|
|
|
12
12
|
const mock = new MockAdapter(api.axiosInstance);
|
|
13
13
|
|
|
14
|
+
const mockTakePhoto = jest.fn().mockResolvedValue({
|
|
15
|
+
path: '/mock/path/photo.jpg',
|
|
16
|
+
width: 1280,
|
|
17
|
+
height: 720,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
jest.mock('react-native-vision-camera', () => {
|
|
21
|
+
const React = require('react');
|
|
22
|
+
const { View } = require('react-native');
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
Camera: React.forwardRef((props, ref) => {
|
|
26
|
+
React.useImperativeHandle(ref, () => ({
|
|
27
|
+
takePhoto: mockTakePhoto,
|
|
28
|
+
}));
|
|
29
|
+
return <View testID="mock-camera" {...props} />;
|
|
30
|
+
}),
|
|
31
|
+
useCameraDevice: () => ({
|
|
32
|
+
id: 'mock-device',
|
|
33
|
+
position: 'back',
|
|
34
|
+
}),
|
|
35
|
+
useCameraPermission: () => ({
|
|
36
|
+
hasPermission: true,
|
|
37
|
+
requestPermission: jest.fn().mockResolvedValue(true),
|
|
38
|
+
}),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
14
42
|
const wrapComponent = (route) => (
|
|
15
43
|
<SCProvider initState={mockSCStore({})}>
|
|
16
44
|
<HanetCaptureFaceID route={route} />
|
|
@@ -23,6 +51,7 @@ describe('Test HanetCaptureFaceID', () => {
|
|
|
23
51
|
|
|
24
52
|
beforeEach(() => {
|
|
25
53
|
mockSetAvatar.mockClear();
|
|
54
|
+
mockTakePhoto.mockClear();
|
|
26
55
|
route = {
|
|
27
56
|
params: {
|
|
28
57
|
title: 'title',
|
|
@@ -46,7 +75,7 @@ describe('Test HanetCaptureFaceID', () => {
|
|
|
46
75
|
await act(async () => {
|
|
47
76
|
await touches[1].props.onPress();
|
|
48
77
|
});
|
|
49
|
-
const camera = instance.findByType(
|
|
78
|
+
const camera = instance.findByType(Camera);
|
|
50
79
|
expect(camera).toBeDefined();
|
|
51
80
|
|
|
52
81
|
// capture
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { RNCamera } from 'react-native-camera';
|
|
3
2
|
import { act, create } from 'react-test-renderer';
|
|
4
3
|
|
|
5
4
|
import { SCProvider } from '../../../../../context';
|
|
6
5
|
import { mockSCStore } from '../../../../../context/mockStore';
|
|
7
6
|
import QRScan from '../index';
|
|
7
|
+
import { Camera } from 'react-native-vision-camera';
|
|
8
8
|
|
|
9
9
|
const mockedOnScan = jest.fn();
|
|
10
10
|
const mockedSetLoading = jest.fn();
|
|
11
11
|
|
|
12
|
+
jest.mock('react-native-vision-camera', () => {
|
|
13
|
+
const React = require('react');
|
|
14
|
+
const { View } = require('react-native');
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
Camera: React.forwardRef((props, ref) => (
|
|
18
|
+
<View testID="mock-camera" {...props} />
|
|
19
|
+
)),
|
|
20
|
+
useCameraDevice: () => ({
|
|
21
|
+
id: 'mock-device',
|
|
22
|
+
position: 'back',
|
|
23
|
+
}),
|
|
24
|
+
useCameraPermission: () => ({
|
|
25
|
+
hasPermission: true,
|
|
26
|
+
requestPermission: jest.fn().mockResolvedValue(true),
|
|
27
|
+
}),
|
|
28
|
+
useCodeScanner: (options) => options,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
12
32
|
const wrapComponent = (data) => (
|
|
13
33
|
<SCProvider initState={mockSCStore({})}>
|
|
14
34
|
<QRScan {...data} />
|
|
@@ -20,6 +40,7 @@ describe('Test QRScan', () => {
|
|
|
20
40
|
|
|
21
41
|
beforeEach(() => {
|
|
22
42
|
Date.now = jest.fn(() => new Date('2021-01-24T12:00:00.000Z'));
|
|
43
|
+
mockedOnScan.mockClear();
|
|
23
44
|
data = {
|
|
24
45
|
onScan: mockedOnScan,
|
|
25
46
|
loading: false,
|
|
@@ -33,21 +54,27 @@ describe('Test QRScan', () => {
|
|
|
33
54
|
tree = await create(wrapComponent(data));
|
|
34
55
|
});
|
|
35
56
|
const instance = tree.root;
|
|
36
|
-
const
|
|
37
|
-
expect(
|
|
57
|
+
const cameras = instance.findAllByType(Camera);
|
|
58
|
+
expect(cameras).toHaveLength(1);
|
|
38
59
|
});
|
|
39
60
|
|
|
40
|
-
it('
|
|
61
|
+
it('onCodeScanned via codeScanner prop', async () => {
|
|
41
62
|
await act(async () => {
|
|
42
63
|
tree = await create(wrapComponent(data));
|
|
43
64
|
});
|
|
44
65
|
const instance = tree.root;
|
|
45
|
-
const
|
|
66
|
+
const camera = instance.findByType(Camera);
|
|
67
|
+
|
|
68
|
+
// Simulate code scanned by calling the codeScanner callback
|
|
69
|
+
const codeScanner = camera.props.codeScanner;
|
|
70
|
+
expect(codeScanner).toBeDefined();
|
|
46
71
|
|
|
47
|
-
const e = { data: JSON.stringify({ id: 1 }) };
|
|
48
72
|
await act(async () => {
|
|
49
|
-
|
|
73
|
+
codeScanner.onCodeScanned([{ value: JSON.stringify({ id: 1 }) }]);
|
|
50
74
|
});
|
|
51
|
-
expect(mockedOnScan).
|
|
75
|
+
expect(mockedOnScan).toHaveBeenCalledWith(
|
|
76
|
+
JSON.stringify({ id: 1 }),
|
|
77
|
+
expect.any(Function)
|
|
78
|
+
);
|
|
52
79
|
});
|
|
53
80
|
});
|
|
@@ -8,7 +8,12 @@ import {
|
|
|
8
8
|
TouchableOpacity,
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
import { IconOutline } from '@ant-design/icons-react-native';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Camera,
|
|
13
|
+
useCameraDevice,
|
|
14
|
+
useCameraPermission,
|
|
15
|
+
useCodeScanner,
|
|
16
|
+
} from 'react-native-vision-camera';
|
|
12
17
|
import { getStatusBarHeight } from 'react-native-iphone-x-helper';
|
|
13
18
|
import { useTranslations } from '../../../../hooks/Common/useTranslations';
|
|
14
19
|
|
|
@@ -52,18 +57,24 @@ const QRScan = ({ isScanReady = true, onScan }) => {
|
|
|
52
57
|
const isFocused = useIsFocused();
|
|
53
58
|
|
|
54
59
|
const [loading, setLoading] = useState(false);
|
|
60
|
+
const { hasPermission, requestPermission } = useCameraPermission();
|
|
61
|
+
const device = useCameraDevice('back');
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!hasPermission) {
|
|
65
|
+
requestPermission();
|
|
66
|
+
}
|
|
67
|
+
}, [hasPermission, requestPermission]);
|
|
55
68
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
const codeScanner = useCodeScanner({
|
|
70
|
+
codeTypes: ['qr', 'ean-13', 'code-128'],
|
|
71
|
+
onCodeScanned: (codes) => {
|
|
72
|
+
if (isScanReady && codes.length > 0 && !loading) {
|
|
59
73
|
setLoading(true);
|
|
60
|
-
|
|
61
|
-
onScan(e.data, setLoading);
|
|
62
|
-
}
|
|
74
|
+
onScan(codes[0].value, setLoading);
|
|
63
75
|
}
|
|
64
76
|
},
|
|
65
|
-
|
|
66
|
-
);
|
|
77
|
+
});
|
|
67
78
|
|
|
68
79
|
const onPressClose = useCallback(() => {
|
|
69
80
|
navigation.pop();
|
|
@@ -73,63 +84,69 @@ const QRScan = ({ isScanReady = true, onScan }) => {
|
|
|
73
84
|
isFocused && setLoading(false);
|
|
74
85
|
}, [isFocused, setLoading]);
|
|
75
86
|
|
|
87
|
+
if (!hasPermission) {
|
|
88
|
+
return <LoadingCamera />;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!device) {
|
|
92
|
+
return <LoadingCamera />;
|
|
93
|
+
}
|
|
94
|
+
|
|
76
95
|
return (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{loading && <VerifyingQRCode />}
|
|
123
|
-
</View>
|
|
124
|
-
);
|
|
125
|
-
}}
|
|
126
|
-
</RNCamera>
|
|
96
|
+
<View style={styles.container}>
|
|
97
|
+
<Camera
|
|
98
|
+
style={StyleSheet.absoluteFill}
|
|
99
|
+
device={device}
|
|
100
|
+
isActive={isFocused}
|
|
101
|
+
codeScanner={codeScanner}
|
|
102
|
+
/>
|
|
103
|
+
<View style={styles.maskOuter}>
|
|
104
|
+
<View
|
|
105
|
+
style={[
|
|
106
|
+
{ flex: maskRowHeight },
|
|
107
|
+
styles.maskRow,
|
|
108
|
+
styles.maskFrame,
|
|
109
|
+
]}
|
|
110
|
+
/>
|
|
111
|
+
<View style={styles.maskCenter}>
|
|
112
|
+
<View style={[{ width: maskColWidth }, styles.maskFrame]} />
|
|
113
|
+
<View style={styles.maskInner} />
|
|
114
|
+
<View style={[{ width: maskColWidth }, styles.maskFrame]} />
|
|
115
|
+
</View>
|
|
116
|
+
<View style={styles.scanningGuide}>
|
|
117
|
+
<Text style={styles.scanningGuideText}>
|
|
118
|
+
{t('qr_scan_guidelines')}
|
|
119
|
+
</Text>
|
|
120
|
+
</View>
|
|
121
|
+
<View
|
|
122
|
+
style={[
|
|
123
|
+
{ flex: maskRowHeight },
|
|
124
|
+
styles.maskRow,
|
|
125
|
+
styles.maskFrame,
|
|
126
|
+
]}
|
|
127
|
+
/>
|
|
128
|
+
<CircleButton
|
|
129
|
+
onPress={onPressClose}
|
|
130
|
+
size={32}
|
|
131
|
+
backgroundColor={Colors.White}
|
|
132
|
+
borderWidth={1}
|
|
133
|
+
borderColor={Colors.Gray4}
|
|
134
|
+
style={styles.buttonClose}
|
|
135
|
+
>
|
|
136
|
+
<IconOutline name={'close'} size={24} />
|
|
137
|
+
</CircleButton>
|
|
138
|
+
{loading && <VerifyingQRCode />}
|
|
139
|
+
</View>
|
|
140
|
+
</View>
|
|
127
141
|
);
|
|
128
142
|
};
|
|
129
143
|
|
|
130
144
|
export default QRScan;
|
|
131
145
|
|
|
132
146
|
const styles = StyleSheet.create({
|
|
147
|
+
container: {
|
|
148
|
+
flex: 1,
|
|
149
|
+
},
|
|
133
150
|
preview: {
|
|
134
151
|
flex: 1,
|
|
135
152
|
justifyContent: 'flex-end',
|