@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.
@@ -39,7 +39,7 @@ buildscript {
39
39
  }
40
40
 
41
41
  android {
42
- namespace "com.eohjsc.smartcity"
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.34",
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 { RNCamera } from 'react-native-camera';
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
- const options = {
44
- quality: 0.7,
45
- height: 1280,
46
- width: 736,
47
- imageType: 'jpg',
48
- pauseAfterCapture: true,
49
- forceUpOrientation: true,
50
- fixOrientation: true,
51
- orientation: 'portrait',
52
- };
53
- const data = await refCamera.current.takePictureAsync(options);
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
- if (refCamera.current) {
61
- setCapturing(true);
62
- refCamera.current.resumePreview();
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
- <RNCamera
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 { RNCamera } from 'react-native-camera';
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(RNCamera);
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 RNCam = instance.findAllByType(RNCamera);
37
- expect(RNCam).toHaveLength(1);
57
+ const cameras = instance.findAllByType(Camera);
58
+ expect(cameras).toHaveLength(1);
38
59
  });
39
60
 
40
- it('onBarCodeRead', async () => {
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 RNCam = instance.findByType(RNCamera);
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
- RNCam.props.onBarCodeRead(e);
73
+ codeScanner.onCodeScanned([{ value: JSON.stringify({ id: 1 }) }]);
50
74
  });
51
- expect(mockedOnScan).toHaveBeenCalled();
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 { RNCamera } from 'react-native-camera';
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 onBarCodeRead = useCallback(
57
- (e) => {
58
- if (isScanReady) {
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
- if (!loading) {
61
- onScan(e.data, setLoading);
62
- }
74
+ onScan(codes[0].value, setLoading);
63
75
  }
64
76
  },
65
- [isScanReady, loading, onScan]
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
- <RNCamera
78
- style={styles.preview}
79
- type={RNCamera.Constants.Type.back}
80
- onBarCodeRead={onBarCodeRead}
81
- >
82
- {({ camera, status, recordAudioPermissionStatus }) => {
83
- if (status !== 'READY') {
84
- return <LoadingCamera />;
85
- }
86
- return (
87
- <View style={styles.maskOuter}>
88
- <View
89
- style={[
90
- { flex: maskRowHeight },
91
- styles.maskRow,
92
- styles.maskFrame,
93
- ]}
94
- />
95
- <View style={styles.maskCenter}>
96
- <View style={[{ width: maskColWidth }, styles.maskFrame]} />
97
- <View style={styles.maskInner} />
98
- <View style={[{ width: maskColWidth }, styles.maskFrame]} />
99
- </View>
100
- <View style={styles.scanningGuide}>
101
- <Text style={styles.scanningGuideText}>
102
- {t('qr_scan_guidelines')}
103
- </Text>
104
- </View>
105
- <View
106
- style={[
107
- { flex: maskRowHeight },
108
- styles.maskRow,
109
- styles.maskFrame,
110
- ]}
111
- />
112
- <CircleButton
113
- onPress={onPressClose}
114
- size={32}
115
- backgroundColor={Colors.White}
116
- borderWidth={1}
117
- borderColor={Colors.Gray4}
118
- style={styles.buttonClose}
119
- >
120
- <IconOutline name={'close'} size={24} co />
121
- </CircleButton>
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',