@eohjsc/react-native-smart-city 0.3.50 → 0.3.52

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 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.3.50",
4
+ "version": "0.3.52",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -148,6 +148,62 @@ describe('Test ItemQuickAction', () => {
148
148
  assertIsActionOn();
149
149
  });
150
150
 
151
+ // eslint-disable-next-line max-len
152
+ it('trigger action with quick action with auto update and allow_config_store_value_id = quick_action.config_id and action.name includes off', async () => {
153
+ sensor.quick_action.will_auto_update_status = true;
154
+ sensor.quick_action.on_action.allow_config_store_value_id =
155
+ sensor.quick_action.config_id;
156
+ sensor.quick_action.on_action.name = 'off_action';
157
+
158
+ await act(async () => {
159
+ tree = await create(
160
+ <ItemQuickAction sensor={sensor} wrapperStyle={style} />
161
+ );
162
+ });
163
+ const instance = tree.root;
164
+ const buttonOnActionPress = instance.find(
165
+ (el) =>
166
+ el.props.accessibilityLabel ===
167
+ `${AccessibilityLabel.ITEM_QUICK_ACTION_PRESS}-${sensor?.id}` &&
168
+ el.type === TouchableOpacity
169
+ );
170
+ await act(async () => {
171
+ await buttonOnActionPress.props.onPress();
172
+ });
173
+ await act(async () => {
174
+ jest.runAllTimers();
175
+ });
176
+ assertIsActionOn();
177
+ });
178
+
179
+ // eslint-disable-next-line max-len
180
+ it('trigger action with quick action with auto update and allow_config_store_value_id = quick_action.config_id and action.name not includes off', async () => {
181
+ sensor.quick_action.will_auto_update_status = true;
182
+ sensor.quick_action.on_action.allow_config_store_value_id =
183
+ sensor.quick_action.config_id;
184
+ sensor.quick_action.on_action.name = 'on_action';
185
+
186
+ await act(async () => {
187
+ tree = await create(
188
+ <ItemQuickAction sensor={sensor} wrapperStyle={style} />
189
+ );
190
+ });
191
+ const instance = tree.root;
192
+ const buttonOnActionPress = instance.find(
193
+ (el) =>
194
+ el.props.accessibilityLabel ===
195
+ `${AccessibilityLabel.ITEM_QUICK_ACTION_PRESS}-${sensor?.id}` &&
196
+ el.type === TouchableOpacity
197
+ );
198
+ await act(async () => {
199
+ await buttonOnActionPress.props.onPress();
200
+ });
201
+ await act(async () => {
202
+ jest.runAllTimers();
203
+ });
204
+ assertIsActionOn();
205
+ });
206
+
151
207
  it('on press will toggle action', async () => {
152
208
  sensor.quick_action.on_action.id = 10; // off action
153
209
  const mockSetStatus = jest.fn();
@@ -13,8 +13,9 @@ const OnOffButtonAction = ({ title, configuration, onPress, template }) => {
13
13
  name: text_on,
14
14
  action: action_on,
15
15
  action_off: null,
16
+ template,
16
17
  });
17
- }, [onPress, configuration, text_on, action_on]);
18
+ }, [onPress, configuration, text_on, action_on, template]);
18
19
 
19
20
  const onPressActionOff = useCallback(() => {
20
21
  onPress &&
@@ -23,8 +24,9 @@ const OnOffButtonAction = ({ title, configuration, onPress, template }) => {
23
24
  name: text_off,
24
25
  action: action_off,
25
26
  action_on: null,
27
+ template,
26
28
  });
27
- }, [onPress, configuration, text_off, action_off]);
29
+ }, [onPress, configuration, text_off, action_off, template]);
28
30
 
29
31
  return (
30
32
  <>
@@ -15,6 +15,7 @@ const OnOffSimpleAction = ({ configuration, onPress, template }) => {
15
15
  name: t('text_on'),
16
16
  action: action_on,
17
17
  action_off: null,
18
+ template,
18
19
  });
19
20
  };
20
21
 
@@ -25,6 +26,7 @@ const OnOffSimpleAction = ({ configuration, onPress, template }) => {
25
26
  name: t('text_off'),
26
27
  action: action_off,
27
28
  action_on: null,
29
+ template,
28
30
  });
29
31
  };
30
32
 
@@ -13,8 +13,9 @@ const OnOffSmartLockAction = ({ configuration, onPress, template }) => {
13
13
  name: text_on,
14
14
  action: action_on,
15
15
  action_off: null,
16
+ template,
16
17
  });
17
- }, [onPress, configuration, text_on, action_on]);
18
+ }, [onPress, configuration, text_on, action_on, template]);
18
19
 
19
20
  const onPressActionClose = useCallback(() => {
20
21
  onPress &&
@@ -23,8 +24,9 @@ const OnOffSmartLockAction = ({ configuration, onPress, template }) => {
23
24
  name: text_off,
24
25
  action: action_off,
25
26
  action_on: null,
27
+ template,
26
28
  });
27
- }, [onPress, configuration, text_off, action_off]);
29
+ }, [onPress, configuration, text_off, action_off, template]);
28
30
 
29
31
  return (
30
32
  <>
@@ -1,17 +1,21 @@
1
1
  import React from 'react';
2
- import renderer, { act } from 'react-test-renderer';
2
+ import { Platform } from 'react-native';
3
+ import { act, create } from 'react-test-renderer';
3
4
 
4
5
  import ImagePicker from '../index';
5
6
  import ButtonPopup from '../../ButtonPopup';
6
7
  import { SCProvider } from '../../../context';
7
8
  import { mockSCStore } from '../../../context/mockStore';
8
9
 
9
- const wrapComponent = (options) => (
10
+ const mockSetImageUrl = jest.fn();
11
+ const mockSetShowImagePicker = jest.fn();
12
+
13
+ const wrapComponent = (options, showImagePicker) => (
10
14
  <SCProvider initState={mockSCStore({})}>
11
15
  <ImagePicker
12
- showImagePicker={true}
13
- setShowImagePicker={''}
14
- setImageUrl={'setImageUrl'}
16
+ showImagePicker={showImagePicker}
17
+ setShowImagePicker={mockSetShowImagePicker}
18
+ setImageUrl={mockSetImageUrl}
15
19
  optionsCapture={options}
16
20
  optionsSelect={{
17
21
  mediaType: 'photo',
@@ -23,14 +27,18 @@ const wrapComponent = (options) => (
23
27
 
24
28
  describe('Test ImagePicker', () => {
25
29
  let tree;
26
- let Platform;
27
- beforeEach(() => {
28
- Platform = require('react-native').Platform;
29
- });
30
+
31
+ const options = {
32
+ mediaType: 'photo',
33
+ compressImageMaxWidth: 1280,
34
+ compressImageMaxHeight: 720,
35
+ compressImageQuality: 0.8,
36
+ };
37
+
30
38
  it('create ImagePicker', async () => {
31
39
  Platform.OS = 'android';
32
40
  await act(async () => {
33
- tree = renderer.create(wrapComponent());
41
+ tree = await create(wrapComponent());
34
42
  });
35
43
  const instance = tree.root;
36
44
  const textInputs = instance.findAllByType(ButtonPopup);
@@ -39,14 +47,8 @@ describe('Test ImagePicker', () => {
39
47
 
40
48
  it('create ImagePicker optionsCapture', async () => {
41
49
  Platform.OS = 'android';
42
- const options = {
43
- mediaType: 'photo',
44
- compressImageMaxWidth: 1280,
45
- compressImageMaxHeight: 720,
46
- compressImageQuality: 0.8,
47
- };
48
50
  await act(async () => {
49
- tree = renderer.create(wrapComponent(options));
51
+ tree = await create(wrapComponent(options));
50
52
  });
51
53
  const instance = tree.root;
52
54
  const textInputs = instance.findAllByType(ButtonPopup);
@@ -54,23 +56,34 @@ describe('Test ImagePicker', () => {
54
56
  await act(async () => {
55
57
  textInputs[0].props.onPressMain();
56
58
  });
59
+ expect(mockSetImageUrl).toBeCalledWith({ path: 'path_from_camera' });
57
60
  });
61
+
58
62
  it('create ImagePicker onChooseFile', async () => {
59
63
  Platform.OS = 'android';
60
- const options = {
61
- mediaType: 'photo',
62
- compressImageMaxWidth: 1280,
63
- compressImageMaxHeight: 720,
64
- compressImageQuality: 0.8,
65
- };
66
64
  await act(async () => {
67
- tree = renderer.create(wrapComponent(options));
65
+ tree = await create(wrapComponent(options));
68
66
  });
69
67
  const instance = tree.root;
70
- const textInputs = instance.findAllByType(ButtonPopup);
71
- expect(textInputs.length).toBe(1);
68
+ const buttonPopup = instance.findByType(ButtonPopup);
69
+ await act(async () => {
70
+ buttonPopup.props.onPressSecondary();
71
+ });
72
+ expect(mockSetImageUrl).toBeCalledWith({
73
+ path: 'file:///data/user/0/com.eohjsc.eohmobile/cache/Camera/80fbbd4b-926d-425f-a081-e21b13f2f7d0.jpg',
74
+ });
75
+ });
76
+
77
+ it('Test onClose', async () => {
78
+ Platform.OS = 'ios';
79
+ await act(async () => {
80
+ tree = await create(wrapComponent(options, true));
81
+ });
82
+ const instance = tree.root;
83
+ const buttonPopup = instance.findByType(ButtonPopup);
72
84
  await act(async () => {
73
- textInputs[0].props.onPressSecondary();
85
+ await buttonPopup.props.onClose();
74
86
  });
87
+ expect(mockSetShowImagePicker).toBeCalledWith(false);
75
88
  });
76
89
  });
@@ -1,10 +1,14 @@
1
- import React, { useCallback } from 'react';
2
- import { Platform, PermissionsAndroid } from 'react-native';
1
+ import React, { memo, useCallback } from 'react';
3
2
  import ImagePickerCrop from 'react-native-image-crop-picker';
3
+ import { RESULTS } from 'react-native-permissions';
4
4
 
5
- import { useTranslations } from '../../hooks/Common/useTranslations';
6
-
5
+ import {
6
+ keyPermission,
7
+ OpenSetting,
8
+ permitPermissionFunction,
9
+ } from '../../utils/Permission/common';
7
10
  import ButtonPopup from '../ButtonPopup';
11
+ import { useTranslations } from '../../hooks/Common/useTranslations';
8
12
 
9
13
  const ImagePicker = ({
10
14
  showImagePicker,
@@ -14,47 +18,6 @@ const ImagePicker = ({
14
18
  optionsSelect,
15
19
  }) => {
16
20
  const t = useTranslations();
17
-
18
- const requestCameraPermission = useCallback(async () => {
19
- if (Platform.OS === 'android') {
20
- try {
21
- const granted = await PermissionsAndroid.request(
22
- PermissionsAndroid.PERMISSIONS.CAMERA,
23
- {
24
- title: 'Camera Permission',
25
- message: 'App needs camera permission',
26
- }
27
- );
28
- // If CAMERA Permission is granted
29
- return granted === PermissionsAndroid.RESULTS.GRANTED;
30
- } catch (err) {
31
- return false;
32
- }
33
- } else {
34
- return true;
35
- }
36
- }, []);
37
-
38
- const requestExternalWritePermission = useCallback(async () => {
39
- if (Platform.OS === 'android') {
40
- try {
41
- const granted = await PermissionsAndroid.request(
42
- PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
43
- {
44
- title: 'External Storage Write Permission',
45
- message: 'App needs write permission',
46
- }
47
- );
48
- // If WRITE_EXTERNAL_STORAGE Permission is granted
49
- return granted === PermissionsAndroid.RESULTS.GRANTED;
50
- } catch (err) {
51
- return false;
52
- }
53
- } else {
54
- return true;
55
- }
56
- }, []);
57
-
58
21
  const captureImage = useCallback(
59
22
  async (type) => {
60
23
  let options = optionsCapture
@@ -65,28 +28,27 @@ const ImagePicker = ({
65
28
  compressImageMaxHeight: 720,
66
29
  compressImageQuality: 0.8,
67
30
  };
68
-
69
- let isCameraPermitted = await requestCameraPermission();
70
- let isStoragePermitted = await requestExternalWritePermission();
71
- if (isCameraPermitted && isStoragePermitted) {
72
- await ImagePickerCrop.openCamera(options)
73
- .then((response) => {
74
- setImageUrl(response);
75
- setShowImagePicker(false);
76
- })
77
- .catch((e) => {
78
- /* eslint-disable no-console */
79
- console.log('ERROR ' + e);
80
- });
81
- }
31
+ permitPermissionFunction(keyPermission.CAMERA, async (result) => {
32
+ if (result === RESULTS.GRANTED) {
33
+ await ImagePickerCrop.openCamera(options)
34
+ .then((response) => {
35
+ setImageUrl(response);
36
+ setShowImagePicker(false);
37
+ })
38
+ .catch((e) => {
39
+ /* eslint-disable no-console */
40
+ console.log('ERROR ' + e);
41
+ });
42
+ }
43
+ if (result === RESULTS.BLOCKED) {
44
+ OpenSetting(
45
+ t('camera_request_permission'),
46
+ t('camera_request_permission_des')
47
+ );
48
+ }
49
+ });
82
50
  },
83
- [
84
- setImageUrl,
85
- setShowImagePicker,
86
- requestCameraPermission,
87
- requestExternalWritePermission,
88
- optionsCapture,
89
- ]
51
+ [optionsCapture, setImageUrl, setShowImagePicker, t]
90
52
  );
91
53
 
92
54
  const chooseFile = useCallback(
@@ -99,17 +61,27 @@ const ImagePicker = ({
99
61
  compressImageMaxHeight: 720,
100
62
  compressImageQuality: 0.8,
101
63
  };
102
- await ImagePickerCrop.openPicker(options)
103
- .then((response) => {
104
- setImageUrl(response);
105
- setShowImagePicker(false);
106
- })
107
- .catch((e) => {
108
- /* eslint-disable no-console */
109
- console.log('ERROR ' + e);
110
- });
64
+ permitPermissionFunction(keyPermission.SELECT_PHOTO, async (result) => {
65
+ if (result === RESULTS.GRANTED || result === RESULTS.LIMITED) {
66
+ await ImagePickerCrop.openPicker(options)
67
+ .then((response) => {
68
+ setImageUrl(response);
69
+ setShowImagePicker(false);
70
+ })
71
+ .catch((e) => {
72
+ /* eslint-disable no-console */
73
+ console.log('ERROR ' + e);
74
+ });
75
+ }
76
+ if (result === RESULTS.BLOCKED) {
77
+ OpenSetting(
78
+ t('photo_request_permission'),
79
+ t('photo_request_permission_des')
80
+ );
81
+ }
82
+ });
111
83
  },
112
- [setImageUrl, setShowImagePicker, optionsSelect]
84
+ [optionsSelect, setImageUrl, setShowImagePicker, t]
113
85
  );
114
86
 
115
87
  const onCaptureImage = useCallback(() => {
@@ -124,25 +96,18 @@ const ImagePicker = ({
124
96
  setShowImagePicker(false);
125
97
  }, [setShowImagePicker]);
126
98
 
127
- const buttonEvent = {
128
- main_title: t('take_photo'),
129
- secondary_title: t('choose_from_library'),
130
- on_press_main: onCaptureImage,
131
- on_press_secondary: onChooseFile,
132
- };
133
-
134
99
  return (
135
100
  <ButtonPopup
136
101
  rowButton={false}
137
102
  visible={showImagePicker}
138
103
  onClose={onClose}
139
- mainTitle={buttonEvent.main_title}
140
- onPressMain={buttonEvent.on_press_main}
141
- secondaryTitle={buttonEvent.secondary_title}
142
- onPressSecondary={buttonEvent.on_press_secondary}
104
+ mainTitle={t('take_photo')}
105
+ onPressMain={onCaptureImage}
106
+ secondaryTitle={t('choose_from_library')}
107
+ onPressSecondary={onChooseFile}
143
108
  typeSecondary={'primary'}
144
109
  />
145
110
  );
146
111
  };
147
112
 
148
- export default ImagePicker;
113
+ export default memo(ImagePicker);
@@ -55,7 +55,7 @@ const SelectGateway = ({
55
55
  const t = useTranslations();
56
56
  const { goBack } = useNavigation();
57
57
  const isFocused = useIsFocused();
58
- const [selectedIndex, setSelectedIndex] = useState(0);
58
+ const [selectedIndex, setSelectedIndex] = useState(-1);
59
59
  const [gateways, setGateways] = useState([]);
60
60
  const [showPopupGuide, setShowPopupGuide, setHidePopupGuide] =
61
61
  useBoolean(false);
@@ -77,15 +77,19 @@ const SelectGateway = ({
77
77
  }
78
78
  }, [setShowPopupGuide, type, unitId]);
79
79
 
80
- const handleOk = () => {
80
+ const handleButtonModalOk = useCallback(() => {
81
81
  setHidePopupGuide();
82
- onPressOk();
83
- };
82
+ onPressOk && onPressOk();
83
+ }, [onPressOk, setHidePopupGuide]);
84
84
 
85
85
  useEffect(() => {
86
86
  isFocused && fetchDetails();
87
87
  }, [fetchDetails, isFocused]);
88
88
 
89
+ const onRightClickNext = useCallback(() => {
90
+ onPressNext && onPressNext(gateways[selectedIndex]);
91
+ }, [gateways, onPressNext, selectedIndex]);
92
+
89
93
  return (
90
94
  <View style={styles.container}>
91
95
  <HeaderCustom title={title} isShowSeparator />
@@ -115,7 +119,7 @@ const SelectGateway = ({
115
119
  onLeftClick={goBack}
116
120
  rightTitle={t('next')}
117
121
  rightDisabled={selectedIndex === -1}
118
- onRightClick={() => onPressNext(gateways[selectedIndex])}
122
+ onRightClick={onRightClickNext}
119
123
  accessibilityLabelPrefix={AccessibilityLabel.PREFIX.SELECT_UNIT}
120
124
  />
121
125
  <ModalCustom // todo Huy - make reused component
@@ -134,7 +138,7 @@ const SelectGateway = ({
134
138
  </View>
135
139
  <ViewButtonBottom
136
140
  rightTitle={t('ok')}
137
- onRightClick={handleOk}
141
+ onRightClick={handleButtonModalOk}
138
142
  styleButton={styles.bottomButton}
139
143
  />
140
144
  </View>
@@ -1,9 +1,12 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
3
 
4
4
  import { Colors } from '../../configs';
5
5
  import Text from '../../commons/Text';
6
6
  import { AccessibilityLabel } from '../../configs/Constants';
7
+ import withPreventDoubleClick from '../WithPreventDoubleClick';
8
+
9
+ const PreventDoubleTouch = withPreventDoubleClick(TouchableOpacity);
7
10
 
8
11
  // this view support either 1 or 2 button
9
12
  const ViewButtonBottom = ({
@@ -19,9 +22,14 @@ const ViewButtonBottom = ({
19
22
  styleButtonLeft,
20
23
  styleButtonRight,
21
24
  accessibilityLabelPrefix = '',
25
+ isPreventDoubleTouch = false,
22
26
  }) => {
23
27
  const useTwoButton = leftTitle && rightTitle;
24
28
 
29
+ const RightButtonView = useMemo(() => {
30
+ return isPreventDoubleTouch ? PreventDoubleTouch : TouchableOpacity;
31
+ }, [isPreventDoubleTouch]);
32
+
25
33
  return (
26
34
  <View style={styles.container}>
27
35
  {leftTitle && (
@@ -47,7 +55,7 @@ const ViewButtonBottom = ({
47
55
  </TouchableOpacity>
48
56
  )}
49
57
  {rightTitle && (
50
- <TouchableOpacity
58
+ <RightButtonView
51
59
  style={[
52
60
  styles.button,
53
61
  styleButton,
@@ -66,7 +74,7 @@ const ViewButtonBottom = ({
66
74
  >
67
75
  {rightTitle}
68
76
  </Text>
69
- </TouchableOpacity>
77
+ </RightButtonView>
70
78
  )}
71
79
  </View>
72
80
  );
@@ -97,6 +97,7 @@ const SCDefaultConfig = {
97
97
  pusherAppKey: '8557fcc63959f564f1aa',
98
98
  pusherAppCluster: 'ap1',
99
99
  intervalWatchConfigTime: 30000,
100
+ setCurrentSensorDisplay: () => {},
100
101
  };
101
102
 
102
103
  export class SCConfig {
@@ -112,6 +113,7 @@ export class SCConfig {
112
113
  static pusherAppCluste = SCDefaultConfig.pusherAppCluster;
113
114
  static language = 'en';
114
115
  static intervalWatchConfigTime = SCDefaultConfig.intervalWatchConfigTime;
116
+ static setCurrentSensorDisplay = SCDefaultConfig.setCurrentSensorDisplay;
115
117
  }
116
118
 
117
119
  export const initSCConfig = (config) => {
@@ -135,4 +137,6 @@ export const initSCConfig = (config) => {
135
137
  config.pusherAppCluster ?? SCDefaultConfig.pusherAppCluster;
136
138
  SCConfig.intervalWatchConfigTime =
137
139
  config.intervalWatchConfigTime ?? SCDefaultConfig.intervalWatchConfigTime;
140
+ SCConfig.setCurrentSensorDisplay =
141
+ config.setCurrentSensorDisplay ?? SCDefaultConfig.setCurrentSensorDisplay;
138
142
  };
@@ -78,6 +78,7 @@ const ConnectingWifiGuide = ({ route }) => {
78
78
  'Eoh@2021',
79
79
  false
80
80
  );
81
+ setCurrentState(1);
81
82
  } catch (e) {
82
83
  Alert.alert(
83
84
  t('cannot_connect_to_device_wifi'),
@@ -6,18 +6,19 @@ import Routes from '../../utils/Route';
6
6
 
7
7
  const SelectZigbeeGateway = ({ route }) => {
8
8
  const t = useTranslations();
9
- const navigation = useNavigation();
9
+ const { navigate } = useNavigation();
10
10
  const { unitId, subUnit } = route?.params || {};
11
11
 
12
12
  const onPressNext = useCallback(
13
13
  (gateway) => {
14
- navigation.navigate(Routes.ZigbeeDeviceConnectGuide, {
15
- unitId,
16
- subUnit,
17
- chipId: gateway.id,
18
- });
14
+ gateway &&
15
+ navigate(Routes.ZigbeeDeviceConnectGuide, {
16
+ unitId,
17
+ subUnit,
18
+ chipId: gateway.id,
19
+ });
19
20
  },
20
- [navigation, unitId, subUnit]
21
+ [unitId, subUnit, navigate]
21
22
  );
22
23
 
23
24
  return (
@@ -55,7 +55,7 @@ import PreventAccess from '../../commons/PreventAccess';
55
55
  import styles from './styles';
56
56
  import Routes from '../../utils/Route';
57
57
  import { getData as getLocalData } from '../../utils/Storage';
58
- import { API, Colors } from '../../configs';
58
+ import { API, Colors, SCConfig } from '../../configs';
59
59
  import { DEVICE_TYPE, AccessibilityLabel } from '../../configs/Constants';
60
60
  import { axiosGet } from '../../utils/Apis/axios';
61
61
  import { notImplemented } from '../../utils/Utils';
@@ -81,7 +81,8 @@ const DeviceDetail = ({ route }) => {
81
81
  // eslint-disable-next-line no-unused-vars
82
82
  const [configValues, setConfigValues] = useConfigGlobalState('configValues');
83
83
 
84
- const { unitData, unitId, sensorData, sensorId } = route?.params || {};
84
+ const { unitData, unitId, sensorData, sensorId, isMyUnitDeviceScreen } =
85
+ route?.params || {};
85
86
  const [unit, setUnit] = useState(unitData || { id: unitId });
86
87
  const [sensor, setSensor] = useState(sensorData || { id: sensorId });
87
88
  const [station, setStation] = useState(sensor?.station);
@@ -116,6 +117,9 @@ const DeviceDetail = ({ route }) => {
116
117
  useBluetoothDeviceConnected(sensor);
117
118
 
118
119
  const isDeviceHasBle = useMemo(() => {
120
+ if (display.items.length === 0) {
121
+ return undefined;
122
+ }
119
123
  const action = display.items.filter((item) => item.type === 'action');
120
124
  if (action.length === 0) {
121
125
  return false;
@@ -348,7 +352,7 @@ const DeviceDetail = ({ route }) => {
348
352
  menuItems.push({
349
353
  text: t('move_to_another_sub_unit'),
350
354
  route: Routes.MoveToAnotherSubUnit,
351
- data: { unit, sensor, station },
355
+ data: { unit, sensor, station, isMyUnitDeviceScreen },
352
356
  });
353
357
  }
354
358
  if (isShowSetupEmergencyContact) {
@@ -428,14 +432,15 @@ const DeviceDetail = ({ route }) => {
428
432
  display.items,
429
433
  isOwner,
430
434
  isShowSetupEmergencyContact,
431
- isShowSetUpSmartLock,
435
+ sideMenu,
432
436
  t,
437
+ isShowSetUpSmartLock,
433
438
  isFavorite,
434
439
  sensor,
435
440
  unit,
436
441
  sensorName,
437
- sideMenu,
438
442
  station,
443
+ isMyUnitDeviceScreen,
439
444
  emergencyDeviceId,
440
445
  addToFavorites,
441
446
  removeFromFavorites,
@@ -751,6 +756,10 @@ const DeviceDetail = ({ route }) => {
751
756
  ]
752
757
  );
753
758
 
759
+ useEffect(() => {
760
+ SCConfig.setCurrentSensorDisplay(sensor);
761
+ }, [sensor]);
762
+
754
763
  return (
755
764
  <View style={styles.wrap}>
756
765
  <WrapHeaderScrollable
@@ -67,7 +67,7 @@ export const useDisconnectedDevice = (
67
67
 
68
68
  const checkNetworkConnect = useCallback(
69
69
  async () => {
70
- if (!isDeviceHasBle) {
70
+ if (isDeviceHasBle === false) {
71
71
  return;
72
72
  }
73
73
  if (isNetworkConnected === false || serverDown) {
@@ -21,7 +21,6 @@ export default StyleSheet.create({
21
21
  flexDirection: 'row',
22
22
  alignItems: 'flex-start',
23
23
  padding: 16,
24
- gap: 16,
25
24
  backgroundColor: Colors.Blue14,
26
25
  borderColor: Colors.Blue15,
27
26
  borderWidth: 1,
@@ -33,15 +33,17 @@ const RowSubUnit = ({ subUnit, isSelected, onSelect }) => {
33
33
  const MoveToAnotherSubUnit = memo(({ route }) => {
34
34
  const t = useTranslations();
35
35
  const { params = {} } = route;
36
- const { unit, sensor, station } = params;
36
+ const { unit, sensor, station, isMyUnitDeviceScreen } = params;
37
37
  const { navigate } = useNavigation();
38
38
  const [selectedSubUnit, setSelectedSubUnit] = useState(
39
39
  unit?.stations?.find((subUnit) => subUnit.id === station.id)
40
40
  );
41
41
 
42
42
  const listStationUnit = useMemo(() => {
43
- return unit?.stations.slice(2) || [];
44
- }, [unit?.stations]);
43
+ return isMyUnitDeviceScreen
44
+ ? unit?.stations || []
45
+ : unit?.stations.slice(2) || [];
46
+ }, [isMyUnitDeviceScreen, unit?.stations]);
45
47
 
46
48
  const handleOnSelect = useCallback((item) => {
47
49
  setSelectedSubUnit(item);
@@ -55,7 +57,12 @@ const MoveToAnotherSubUnit = memo(({ route }) => {
55
57
  }
56
58
  );
57
59
  if (success) {
58
- navigate(Routes.UnitDetail);
60
+ navigate(Routes.UnitStack, {
61
+ screen: Routes.UnitDetail,
62
+ params: {
63
+ unitId: unit.id,
64
+ },
65
+ });
59
66
  }
60
67
  }, [navigate, selectedSubUnit?.id, sensor?.id, station?.id, unit?.id]);
61
68
 
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { act, renderHook } from '@testing-library/react-hooks';
3
+ import { SCProvider } from '../../../context';
4
+ import { mockSCStore } from '../../../context/mockStore';
5
+ import { useStarredScript } from '../hooks/useStarredScript';
6
+ import MockAdapter from 'axios-mock-adapter';
7
+ import api from '../../../utils/Apis/axios';
8
+ import { API } from '../../../configs';
9
+ import { Action } from '../../../context/actionType';
10
+
11
+ const mockedSetAction = jest.fn();
12
+ const mock = new MockAdapter(api.axiosInstance);
13
+
14
+ const wrapper = ({ children }) => <SCProvider>{children}</SCProvider>;
15
+
16
+ const mockUseContext = jest.fn().mockImplementation(() => ({
17
+ stateData: mockSCStore({}),
18
+ setAction: mockedSetAction,
19
+ }));
20
+
21
+ React.useContext = mockUseContext;
22
+
23
+ jest.mock('react-native-deep-linking');
24
+
25
+ describe('Test useStarredScript', async () => {
26
+ let props;
27
+ beforeEach(() => {
28
+ mockedSetAction.mockClear();
29
+ props = {
30
+ name: 'AutomateScript',
31
+ id: 1,
32
+ script: { id: 2, name: 'ScriptName' },
33
+ };
34
+ });
35
+
36
+ it('test send remote command action null', async () => {
37
+ mock.onPost(API.AUTOMATE.UNSTAR_SCRIPT(props.id)).reply(200);
38
+ const { result } = renderHook(() => useStarredScript(props), {
39
+ wrapper,
40
+ });
41
+ await act(async () => {
42
+ await result.current.unstarScript();
43
+ });
44
+ expect(mockedSetAction).toBeCalledWith(Action.UNSTAR_SCRIPTS, [2]);
45
+ });
46
+ });
@@ -239,6 +239,7 @@ const AddSubUnit = ({ route }) => {
239
239
  rightTitle={t('done')}
240
240
  rightDisabled={validateData}
241
241
  onRightClick={goDone}
242
+ isPreventDoubleTouch
242
243
  />
243
244
  </View>
244
245
  </TouchableWithoutFeedback>
@@ -22,6 +22,7 @@ const MyUnitDevice = ({ device, unit }) => {
22
22
  params: {
23
23
  unitData: unit,
24
24
  sensorData: device,
25
+ isMyUnitDeviceScreen: true,
25
26
  },
26
27
  });
27
28
  }, [navigate, device, unit]);
@@ -19,6 +19,7 @@ import { useReceiveNotifications } from '../../../hooks';
19
19
  import { SCProvider } from '../../../context';
20
20
  import { mockSCStore } from '../../../context/mockStore';
21
21
  import api from '../../../utils/Apis/axios';
22
+ import { WrapHeaderScrollable } from '../../../commons';
22
23
 
23
24
  const mock = new MockAdapter(api.axiosInstance);
24
25
 
@@ -57,8 +58,7 @@ describe('Test UnitSummary', () => {
57
58
  let route;
58
59
 
59
60
  beforeEach(() => {
60
- mock.resetHistory();
61
-
61
+ mock.reset();
62
62
  Date.now = jest.fn(() => new Date('2021-01-24T12:00:00.000Z'));
63
63
  route = {
64
64
  params: {
@@ -144,6 +144,22 @@ describe('Test UnitSummary', () => {
144
144
  expect(componentName.props.unit).toEqual({ id: 1 });
145
145
  });
146
146
 
147
+ it('render fetchUnitSummary failure', async () => {
148
+ route.params.summaryData = null;
149
+ jest.useFakeTimers();
150
+ mock.onGet(API.UNIT.UNIT_SUMMARY_DETAIL(1, 1)).reply(200, { data: {} });
151
+ mock.onGet(API.UNIT.UNIT_SUMMARY(1)).reply(400);
152
+ await act(async () => {
153
+ tree = await create(wrapComponent(route));
154
+ });
155
+ await act(async () => {
156
+ jest.runOnlyPendingTimers();
157
+ });
158
+ const instance = tree.root;
159
+ const wapHeaderScrollable = instance.findByType(WrapHeaderScrollable);
160
+ expect(wapHeaderScrollable.props.children).toBe(null);
161
+ });
162
+
147
163
  it('render fetchUnitDetail success', async () => {
148
164
  route = {
149
165
  params: {
@@ -275,4 +291,17 @@ describe('Test UnitSummary', () => {
275
291
  }
276
292
  });
277
293
  });
294
+
295
+ it('Test render without params', async () => {
296
+ useReceiveNotifications.mockImplementationOnce(() => ({
297
+ dataNotification: {},
298
+ }));
299
+ await act(async () => {
300
+ tree = await create(wrapComponent(route));
301
+ });
302
+ const instance = tree.root;
303
+ const wapHeaderScrollable = instance.findByType(WrapHeaderScrollable);
304
+ expect(wapHeaderScrollable.props.title).toBe('');
305
+ expect(wapHeaderScrollable.props.subTitle).toBe(null);
306
+ });
278
307
  });
@@ -1096,5 +1096,7 @@
1096
1096
  "read_config_permission_error": "You don't have permission to read this config",
1097
1097
  "continue_to_wait": "Continue to wait?",
1098
1098
  "it_has_been_5_minutes": "It has been 5 minutes...",
1099
- "click_here_to_setup_device": "Click here to setup device"
1099
+ "click_here_to_setup_device": "Click here to setup device",
1100
+ "photo_request_permission": "Photo request permission",
1101
+ "photo_request_permission_des": "To select photo from gallery, please unlock access photo permission"
1100
1102
  }
@@ -1094,5 +1094,7 @@
1094
1094
  "read_config_permission_error": "Bạn không có quyền đọc cấu hình này",
1095
1095
  "continue_to_wait": "Bạn có muốn đợi tiếp?",
1096
1096
  "it_has_been_5_minutes": "Đã 5 phút trôi qua...",
1097
- "click_here_to_setup_device": "Chọn thiết bị thêm vào phòng"
1097
+ "click_here_to_setup_device": "Chọn thiết bị thêm vào phòng",
1098
+ "photo_request_permission": "Quyền yêu cầu ảnh",
1099
+ "photo_request_permission_des": "Để chọn ảnh từ thư viện, vui lòng mở khóa quyền truy cập ảnh"
1098
1100
  }