@eohjsc/react-native-smart-city 0.7.35 → 0.7.37
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/package.json +6 -1
- package/src/commons/Device/ItemDevice.js +1 -1
- package/src/commons/Header/Styles/HeaderCustomStyles.js +1 -1
- package/src/commons/HeaderAni/index.js +1 -1
- package/src/commons/MenuActionAddnew/index.js +20 -32
- package/src/commons/Unit/HeaderUnit/index.js +1 -2
- package/src/commons/WrapParallaxScrollView/index.js +1 -2
- package/src/configs/Device.js +1 -1
- package/src/screens/Automate/index.js +5 -5
- package/src/screens/HanetCamera/CaptureFaceID.js +69 -40
- package/src/screens/HanetCamera/__test__/CaptureFaceID.test.js +31 -2
- package/src/screens/Notification/index.js +6 -2
- package/src/screens/ScanChipQR/components/QRScan/__test__/QRScan.test.js +35 -8
- package/src/screens/ScanChipQR/components/QRScan/index.js +76 -59
- package/src/screens/SharedUnit/index.js +2 -1
- package/src/screens/SubUnit/AddSubUnitStyles.js +1 -1
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.37",
|
|
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
|
},
|
|
@@ -10,7 +10,7 @@ import { AccessibilityLabel } from '../../configs/Constants';
|
|
|
10
10
|
|
|
11
11
|
const screenHeight = Constants.height;
|
|
12
12
|
const default_height = 45;
|
|
13
|
-
const paddingIos = getStatusBarHeight() +
|
|
13
|
+
const paddingIos = getStatusBarHeight() + 30;
|
|
14
14
|
export const title_height = 44;
|
|
15
15
|
export const heightHeader = default_height + title_height + paddingIos;
|
|
16
16
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { memo, useCallback } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
4
4
|
|
|
5
5
|
import { Colors } from '../../configs';
|
|
@@ -8,7 +8,6 @@ import ImageButton from '../ImageButton';
|
|
|
8
8
|
import { ModalCustom } from '../Modal';
|
|
9
9
|
import packageJson from '../../../package.json';
|
|
10
10
|
|
|
11
|
-
const keyExtractor = (item) => item.id.toString();
|
|
12
11
|
const MenuActionAddnew = memo(
|
|
13
12
|
({ visible, hideModal, dataActions, onItemClick }) => {
|
|
14
13
|
const t = useTranslations();
|
|
@@ -21,25 +20,6 @@ const MenuActionAddnew = memo(
|
|
|
21
20
|
[onItemClick]
|
|
22
21
|
);
|
|
23
22
|
|
|
24
|
-
const renderItem = useCallback(
|
|
25
|
-
({ item }) => {
|
|
26
|
-
return (
|
|
27
|
-
<View
|
|
28
|
-
style={{
|
|
29
|
-
...styles.action,
|
|
30
|
-
flexBasis: `${100 / numColumns}%`,
|
|
31
|
-
}}
|
|
32
|
-
>
|
|
33
|
-
<ImageButton onPress={() => onPress(item)} image={item.image} />
|
|
34
|
-
<View style={styles.actionTextWrap}>
|
|
35
|
-
<Text styles={styles.actionText}>{item.text}</Text>
|
|
36
|
-
</View>
|
|
37
|
-
</View>
|
|
38
|
-
);
|
|
39
|
-
},
|
|
40
|
-
[numColumns, onPress]
|
|
41
|
-
);
|
|
42
|
-
|
|
43
23
|
return (
|
|
44
24
|
<ModalCustom
|
|
45
25
|
isVisible={visible}
|
|
@@ -53,15 +33,23 @@ const MenuActionAddnew = memo(
|
|
|
53
33
|
<Text style={styles.modalHeaderText}>{t('add_new')}</Text>
|
|
54
34
|
</View>
|
|
55
35
|
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
36
|
+
<View style={styles.actionWrapper}>
|
|
37
|
+
{dataActions.map((item) => (
|
|
38
|
+
<View
|
|
39
|
+
key={item.id.toString()}
|
|
40
|
+
style={[styles.action, { width: `${100 / numColumns}%` }]}
|
|
41
|
+
>
|
|
42
|
+
<ImageButton
|
|
43
|
+
onPress={() => onPress(item)}
|
|
44
|
+
image={item.image}
|
|
45
|
+
/>
|
|
46
|
+
<View style={styles.actionTextWrap}>
|
|
47
|
+
<Text style={styles.actionText}>{item.text}</Text>
|
|
48
|
+
</View>
|
|
49
|
+
</View>
|
|
50
|
+
))}
|
|
51
|
+
</View>
|
|
52
|
+
|
|
65
53
|
<View style={styles.modalHeader}>
|
|
66
54
|
<Text
|
|
67
55
|
style={styles.modalHeaderText}
|
|
@@ -88,7 +76,6 @@ const styles = StyleSheet.create({
|
|
|
88
76
|
borderRadius: 10,
|
|
89
77
|
},
|
|
90
78
|
modalWrapper: {
|
|
91
|
-
flex: 1,
|
|
92
79
|
flexDirection: 'column',
|
|
93
80
|
backgroundColor: Colors.White,
|
|
94
81
|
borderRadius: 10,
|
|
@@ -110,7 +97,8 @@ const styles = StyleSheet.create({
|
|
|
110
97
|
},
|
|
111
98
|
actionWrapper: {
|
|
112
99
|
padding: 16,
|
|
113
|
-
|
|
100
|
+
flexDirection: 'row',
|
|
101
|
+
flexWrap: 'wrap',
|
|
114
102
|
},
|
|
115
103
|
action: {
|
|
116
104
|
marginBottom: 25,
|
|
@@ -106,8 +106,7 @@ const HeaderUnit = memo(({ transparent, unit }) => {
|
|
|
106
106
|
|
|
107
107
|
export default HeaderUnit;
|
|
108
108
|
|
|
109
|
-
const stickyHeaderHeight =
|
|
110
|
-
Device.TopbarHeight + (Device.isIOS ? 0 : StatusBar.currentHeight);
|
|
109
|
+
const stickyHeaderHeight = Device.TopbarHeight + (Device.isIOS ? 0 : 30);
|
|
111
110
|
|
|
112
111
|
const styles = StyleSheet.create({
|
|
113
112
|
container: {
|
|
@@ -10,8 +10,7 @@ import FImage from '../FImage';
|
|
|
10
10
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
11
11
|
import ParallaxScrollView from '../../libs/react-native-parallax-scroll-view';
|
|
12
12
|
|
|
13
|
-
const stickyHeaderHeight =
|
|
14
|
-
Device.TopbarHeight + (Device.isIOS ? 0 : StatusBar.currentHeight);
|
|
13
|
+
const stickyHeaderHeight = Device.TopbarHeight + (Device.isIOS ? 0 : 30);
|
|
15
14
|
|
|
16
15
|
const WrapParallaxScrollView = ({ children, unit, onRefresh }) => {
|
|
17
16
|
const { name = '', background } = unit;
|
package/src/configs/Device.js
CHANGED
|
@@ -17,6 +17,6 @@ export default {
|
|
|
17
17
|
isIOS: Platform.OS === 'ios',
|
|
18
18
|
isIphoneX,
|
|
19
19
|
ToolbarHeight: isIphoneX ? 44 : Platform.OS === 'ios' ? 20 : 0,
|
|
20
|
-
TopbarHeight: isIphoneX ? 88 : Platform.OS === 'ios' ? 64 :
|
|
20
|
+
TopbarHeight: isIphoneX ? 88 : Platform.OS === 'ios' ? 64 : 50, //incule toolbar
|
|
21
21
|
androidSmallHeight: Platform.OS === 'android' && height < 600,
|
|
22
22
|
};
|
|
@@ -34,7 +34,6 @@ const Automate = () => {
|
|
|
34
34
|
const isFocused = useIsFocused();
|
|
35
35
|
const { setOptions, navigate } = useNavigation();
|
|
36
36
|
const [automatesData, setAutomatesData] = useState([]);
|
|
37
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
38
37
|
const { name: currentRouteName } = useRoute();
|
|
39
38
|
const starredScriptIds = useSCContextSelector(
|
|
40
39
|
(state) => state.automate.starredScriptIds
|
|
@@ -55,8 +54,11 @@ const Automate = () => {
|
|
|
55
54
|
}, [automatesData, starredScriptIds]);
|
|
56
55
|
|
|
57
56
|
const getAutomates = useCallback(async () => {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const { success, data } = await axiosGet(
|
|
58
|
+
API.AUTOMATE.GET_SMART(),
|
|
59
|
+
{},
|
|
60
|
+
true
|
|
61
|
+
);
|
|
60
62
|
if (success && data && data.length) {
|
|
61
63
|
const multiUnit = data[0];
|
|
62
64
|
const haveAutomates = data
|
|
@@ -67,7 +69,6 @@ const Automate = () => {
|
|
|
67
69
|
.filter((unit) => !unit.automates.length);
|
|
68
70
|
setAutomatesData([multiUnit, ...haveAutomates, ...notHaveAutomates]);
|
|
69
71
|
}
|
|
70
|
-
setIsLoading(false);
|
|
71
72
|
}, []);
|
|
72
73
|
|
|
73
74
|
const onPressItem = useCallback(
|
|
@@ -224,7 +225,6 @@ const Automate = () => {
|
|
|
224
225
|
|
|
225
226
|
return (
|
|
226
227
|
<View style={styles.wrap}>
|
|
227
|
-
{isLoading && <Loading />}
|
|
228
228
|
<FlatList
|
|
229
229
|
keyExtractor={keyExtractor}
|
|
230
230
|
data={sortedAutomateData}
|
|
@@ -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
|
|
@@ -51,7 +51,9 @@ const Notification = memo(() => {
|
|
|
51
51
|
|
|
52
52
|
const fetchNotifications = useCallback(async (pageParam) => {
|
|
53
53
|
const { success, data } = await axiosGet(
|
|
54
|
-
API.NOTIFICATION.LIST_EOH_NOTIFICATIONS(pageParam)
|
|
54
|
+
API.NOTIFICATION.LIST_EOH_NOTIFICATIONS(pageParam),
|
|
55
|
+
{},
|
|
56
|
+
true
|
|
55
57
|
);
|
|
56
58
|
if (success) {
|
|
57
59
|
setNotifications((preState) => preState.concat(data.results));
|
|
@@ -69,7 +71,9 @@ const Notification = memo(() => {
|
|
|
69
71
|
const onRefresh = useCallback(async () => {
|
|
70
72
|
setPage(1);
|
|
71
73
|
const { success, data } = await axiosGet(
|
|
72
|
-
API.NOTIFICATION.LIST_EOH_NOTIFICATIONS(1)
|
|
74
|
+
API.NOTIFICATION.LIST_EOH_NOTIFICATIONS(1),
|
|
75
|
+
{},
|
|
76
|
+
true
|
|
73
77
|
);
|
|
74
78
|
if (success) {
|
|
75
79
|
setNotifications(data.results);
|
|
@@ -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',
|