@eohjsc/react-native-smart-city 0.4.56 → 0.4.58

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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/commons/ActionTemplate/OnOffSimpleAction.js +4 -1
  3. package/src/commons/ActionTemplate/index.js +2 -1
  4. package/src/commons/Calendar/styles.js +1 -0
  5. package/src/commons/Header/HeaderCustom.js +22 -18
  6. package/src/commons/Header/Styles/HeaderCustomStyles.js +3 -1
  7. package/src/commons/HeaderAni/index.js +3 -2
  8. package/src/commons/OneTapTemplate/NumberUpDownActionTemplate.js +3 -1
  9. package/src/commons/OneTapTemplate/OptionsDropdownActionTemplate.js +2 -0
  10. package/src/commons/OneTapTemplate/StatesGridActionTemplate.js +1 -0
  11. package/src/commons/Unit/HeaderUnit/index.js +23 -20
  12. package/src/commons/Unit/__test__/HeaderUnit.test.js +0 -8
  13. package/src/commons/WheelDateTimePicker/styles.js +1 -0
  14. package/src/configs/AccessibilityLabel.js +1 -0
  15. package/src/screens/Automate/AddNewAction/ChooseAction.js +18 -18
  16. package/src/screens/Automate/AddNewAction/ChooseConfig.js +64 -32
  17. package/src/screens/Automate/AddNewAction/SelectMonitorDevices.js +8 -3
  18. package/src/screens/Automate/AddNewAction/Styles/SelectActionStyles.js +6 -0
  19. package/src/screens/Automate/AddNewAction/__test__/ChooseAction.test.js +73 -0
  20. package/src/screens/Automate/ScriptDetail/index.js +3 -2
  21. package/src/screens/Automate/SetSchedule/styles/indexStyles.js +1 -0
  22. package/src/screens/Device/__test__/detail.test.js +1 -0
  23. package/src/screens/Device/__test__/sensorDisplayItem.test.js +109 -0
  24. package/src/screens/Device/components/SensorDisplayItem.js +42 -0
  25. package/src/screens/Device/detail.js +11 -8
  26. package/src/screens/Device/styles.js +2 -0
  27. package/src/screens/SelectUnit/index.js +3 -2
  28. package/src/screens/Template/__test__/GatewayList.test.js +1 -1
  29. package/src/screens/Template/__test__/Information.test.js +1 -1
  30. package/src/utils/I18n/translations/en.js +3 -0
  31. package/src/utils/I18n/translations/vi.js +3 -0
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.4.56",
4
+ "version": "0.4.58",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -40,7 +40,10 @@ const OnOffSimpleAction = ({ configuration, onPress, template }) => {
40
40
  {t('text_on')}
41
41
  </Text>
42
42
  </TouchableOpacity>
43
- <TouchableOpacity onPress={onPressActionOff}>
43
+ <TouchableOpacity
44
+ accessibilityLabel={AccessibilityLabel.ON_OFF_SIMPLE_ACTION_OFF}
45
+ onPress={onPressActionOff}
46
+ >
44
47
  <Text type="H4" style={styles.textWithLine}>
45
48
  {t('text_off')}
46
49
  </Text>
@@ -37,13 +37,14 @@ const ActionTemplate = memo(({ device, item, onSelectAction }) => {
37
37
  setActionName(action?.name);
38
38
  onSelectAction &&
39
39
  onSelectAction({
40
+ index: item?.index,
40
41
  action_name: action?.name,
41
42
  action: action?.action,
42
43
  data: actionData,
43
44
  template: action?.template,
44
45
  });
45
46
  },
46
- [device?.device_type, onSelectAction]
47
+ [device?.device_type, item?.index, onSelectAction]
47
48
  );
48
49
 
49
50
  const renderAction = useMemo(() => {
@@ -4,6 +4,7 @@ export default StyleSheet.create({
4
4
  calendar: {
5
5
  borderTopLeftRadius: 20,
6
6
  borderTopRightRadius: 20,
7
+ marginBottom: 50,
7
8
  },
8
9
  arrowRight: {
9
10
  transform: [{ rotate: '180deg' }],
@@ -50,14 +50,13 @@ const HeaderCustom = ({
50
50
  style={[styles.wrap, isShowSeparator && styles.separator, headerStyles]}
51
51
  >
52
52
  {isCanBack && (
53
- <TouchableOpacity
54
- style={[styles.buttonBack, buttonBackStyles]}
55
- onPress={handleGoback}
56
- >
57
- <Image
58
- source={iconLeft || Images.arrowBack}
59
- style={[styles.iconBack, iconBackStyle]}
60
- />
53
+ <TouchableOpacity onPress={handleGoback}>
54
+ <View style={[styles.buttonBack, buttonBackStyles]}>
55
+ <Image
56
+ source={iconLeft || Images.arrowBack}
57
+ style={[styles.iconBack, iconBackStyle]}
58
+ />
59
+ </View>
61
60
  </TouchableOpacity>
62
61
  )}
63
62
  <View style={[styles.wrapTitle, wrapTitleStyle]}>
@@ -83,27 +82,32 @@ const HeaderCustom = ({
83
82
  {isShowRight && (
84
83
  <>
85
84
  {!isDisableRefresh && (
86
- <TouchableOpacity style={styles.buttonBack} onPress={onRefresh}>
87
- <Image
88
- source={Images.refresh}
89
- style={styles.iconFresh}
90
- resizeMode={'contain'}
91
- />
85
+ <TouchableOpacity onPress={onRefresh}>
86
+ <View style={styles.buttonBack}>
87
+ <Image
88
+ source={Images.refresh}
89
+ style={styles.iconFresh}
90
+ resizeMode={'contain'}
91
+ />
92
+ </View>
92
93
  </TouchableOpacity>
93
94
  )}
94
95
  <TouchableOpacity
95
- style={styles.buttonBack}
96
96
  onPress={handleShowMenuAction}
97
97
  ref={refMenuAction}
98
98
  accessibilityLabel={AccessibilityLabel.MENU_POPPER_MORE}
99
99
  >
100
- <Icon name={'more'} size={27} color={Colors.Black} />
100
+ <View style={styles.buttonBack}>
101
+ <Icon name={'more'} size={27} color={Colors.Black} />
102
+ </View>
101
103
  </TouchableOpacity>
102
104
  </>
103
105
  )}
104
106
  {isShowClose && (
105
- <TouchableOpacity style={styles.buttonAdd} onPress={handleClose}>
106
- <Icon name={'close'} size={24} color={Colors.Black} />
107
+ <TouchableOpacity onPress={handleClose}>
108
+ <View style={styles.buttonAdd}>
109
+ <Icon name={'close'} size={24} color={Colors.Black} />
110
+ </View>
107
111
  </TouchableOpacity>
108
112
  )}
109
113
  {rightComponent}
@@ -21,13 +21,15 @@ export default StyleSheet.create({
21
21
  height: 18,
22
22
  },
23
23
  buttonBack: {
24
- width: 35,
24
+ width: 45,
25
25
  height: 40,
26
26
  justifyContent: 'center',
27
27
  alignItems: 'center',
28
28
  },
29
29
  buttonAdd: {
30
30
  paddingRight: 6,
31
+ width: 45,
32
+ height: 40,
31
33
  justifyContent: 'center',
32
34
  alignItems: 'center',
33
35
  },
@@ -91,11 +91,12 @@ const HeaderAni = memo(
91
91
  ]}
92
92
  >
93
93
  <TouchableOpacity
94
- style={styles.btnBack}
95
94
  onPress={onPressLeft}
96
95
  accessibilityLabel={AccessibilityLabel.ICON_BACK}
97
96
  >
98
- <Icon name={'left'} size={27} color={Colors.Gray9} />
97
+ <View style={styles.btnBack}>
98
+ <Icon name={'left'} size={27} color={Colors.Gray9} />
99
+ </View>
99
100
  </TouchableOpacity>
100
101
  <View styles={styles.wrapRightComponent}>{rightComponent}</View>
101
102
  </Animated.View>
@@ -75,16 +75,18 @@ const NumberUpDownActionTemplate = ({ device, item = {}, onSelectAction }) => {
75
75
  setActionName(text_format.replace('{number}', value));
76
76
  onSelectAction &&
77
77
  onSelectAction({
78
+ index: item.index,
78
79
  action,
79
80
  data: actionData,
80
81
  template,
81
82
  });
82
83
  onClose();
83
84
  }, [
85
+ value,
84
86
  device?.device_type,
85
87
  text_format,
86
- value,
87
88
  onSelectAction,
89
+ item.index,
88
90
  action,
89
91
  template,
90
92
  allow_config_store_value,
@@ -45,6 +45,7 @@ const OptionsDropdownActionTemplate = ({ device, item, onSelectAction }) => {
45
45
 
46
46
  onSelectAction &&
47
47
  onSelectAction({
48
+ index: item.index,
48
49
  action,
49
50
  data: actionData,
50
51
  template,
@@ -58,6 +59,7 @@ const OptionsDropdownActionTemplate = ({ device, item, onSelectAction }) => {
58
59
  config,
59
60
  device?.device_type,
60
61
  onSelectAction,
62
+ item.index,
61
63
  action,
62
64
  template,
63
65
  ]);
@@ -25,6 +25,7 @@ const ActionItem = ({
25
25
 
26
26
  onSelectAction &&
27
27
  onSelectAction({
28
+ index: item.index,
28
29
  action: item.action,
29
30
  data: actionData,
30
31
  template,
@@ -44,14 +44,15 @@ const HeaderUnit = memo(
44
44
  <View style={[styles.container, bottomBorder && styles.bottomBorder]}>
45
45
  <TouchableOpacity
46
46
  accessibilityLabel={AccessibilityLabel.HEADER_UNIT_BUTTON_BACK}
47
- style={styles.btnLeft}
48
47
  onPress={onPressBack}
49
48
  >
50
- <Icon
51
- name={'left'}
52
- size={27}
53
- color={transparent ? Colors.White : Colors.Black}
54
- />
49
+ <View style={styles.btnLeft}>
50
+ <Icon
51
+ name={'left'}
52
+ size={27}
53
+ color={transparent ? Colors.White : Colors.Black}
54
+ />
55
+ </View>
55
56
  </TouchableOpacity>
56
57
  <View style={[styles.boxTitle, styleBoxTitle]}>
57
58
  {title && (
@@ -66,27 +67,29 @@ const HeaderUnit = memo(
66
67
  {!hideRightPlus && (
67
68
  <TouchableOpacity
68
69
  accessibilityLabel={AccessibilityLabel.HEADER_UNIT_BUTTON_ADD}
69
- style={styles.btnAdd}
70
70
  onPress={onPressAdd}
71
71
  >
72
- <Icon
73
- name={'plus'}
74
- size={27}
75
- color={transparent ? Colors.White : Colors.Black}
76
- />
72
+ <View style={styles.btnAdd}>
73
+ <Icon
74
+ name={'plus'}
75
+ size={27}
76
+ color={transparent ? Colors.White : Colors.Black}
77
+ />
78
+ </View>
77
79
  </TouchableOpacity>
78
80
  )}
79
81
  <TouchableOpacity
80
- style={styles.btnMore}
81
82
  onPress={onPressMore}
82
83
  ref={buttonMoreRef}
83
84
  accessibilityLabel={idButtonMore}
84
85
  >
85
- <Icon
86
- name={'more'}
87
- size={27}
88
- color={transparent ? Colors.White : Colors.Black}
89
- />
86
+ <View style={styles.btnMore}>
87
+ <Icon
88
+ name={'more'}
89
+ size={27}
90
+ color={transparent ? Colors.White : Colors.Black}
91
+ />
92
+ </View>
90
93
  </TouchableOpacity>
91
94
  </View>
92
95
  )}
@@ -108,11 +111,11 @@ const styles = StyleSheet.create({
108
111
  paddingTop: 15,
109
112
  },
110
113
  btnLeft: {
114
+ width: 40,
111
115
  height: '100%',
112
116
  paddingHorizontal: 8,
113
117
  justifyContent: 'center',
114
118
  alignItems: 'center',
115
- width: 40,
116
119
  },
117
120
  boxRight: {
118
121
  flexDirection: 'row',
@@ -127,10 +130,10 @@ const styles = StyleSheet.create({
127
130
  },
128
131
  btnMore: {
129
132
  height: '100%',
133
+ width: 40,
130
134
  justifyContent: 'center',
131
135
  alignItems: 'center',
132
136
  marginRight: 16,
133
- width: 40,
134
137
  },
135
138
  txtHeader: {
136
139
  fontSize: 16,
@@ -23,13 +23,6 @@ describe('Test HeaderUnit', () => {
23
23
  const mockedOnBack = jest.fn();
24
24
  it('HeaderUnit onPress', async () => {
25
25
  let title = 'title';
26
- let style = {
27
- height: '100%',
28
- paddingHorizontal: 8,
29
- justifyContent: 'center',
30
- alignItems: 'center',
31
- width: 40,
32
- };
33
26
  await act(async () => {
34
27
  tree = await create(
35
28
  <HeaderUnit
@@ -46,7 +39,6 @@ describe('Test HeaderUnit', () => {
46
39
  el.props.accessibilityLabel ===
47
40
  AccessibilityLabel.HEADER_UNIT_BUTTON_BACK
48
41
  );
49
- expect(item.props.style).toEqual(style);
50
42
  await act(async () => {
51
43
  item.props.onPress();
52
44
  });
@@ -6,6 +6,7 @@ export default StyleSheet.create({
6
6
  justifyContent: 'center',
7
7
  alignItems: 'center',
8
8
  height: 180,
9
+ marginBottom: 50,
9
10
  },
10
11
  picker: {
11
12
  width: 120,
@@ -562,6 +562,7 @@ export default {
562
562
 
563
563
  // OnOffSimpleAction
564
564
  ON_OFF_SIMPLE_ACTION_ON: 'ON_OFF_SIMPLE_ACTION_ON',
565
+ ON_OFF_SIMPLE_ACTION_OFF: 'ON_OFF_SIMPLE_ACTION_OFF',
565
566
 
566
567
  // Add New Device LG
567
568
  ADD_NEW_DEVICE_LG_ADD: 'ADD_NEW_DEVICE_LG_ADD',
@@ -14,7 +14,8 @@ import NewActionWrapper from './NewActionWrapper';
14
14
  import moment from 'moment';
15
15
  import { ToastBottomHelper } from '../../../utils/Utils';
16
16
 
17
- const RenderActionItem = ({ device, item, handleOnSelectAction }) => {
17
+ const RenderActionItem = ({ device, item, handleOnSelectAction, index }) => {
18
+ item.index = index;
18
19
  switch (item.template) {
19
20
  case 'on_off_button_action_template':
20
21
  case 'one_button_action_template':
@@ -64,13 +65,8 @@ const RenderActionItem = ({ device, item, handleOnSelectAction }) => {
64
65
  const ChooseAction = ({ route }) => {
65
66
  const t = useTranslations();
66
67
  const { navigate } = useNavigation();
67
- const {
68
- unitId,
69
- device,
70
- automateId,
71
- index = -1,
72
- numberActionCanAdd,
73
- } = route?.params || {};
68
+ const { unitId, device, automateId, numberActionCanAdd } =
69
+ route?.params || {};
74
70
  const [data, setData] = useState([]);
75
71
  const [actions, setActions] = useState([]);
76
72
 
@@ -117,14 +113,18 @@ const ChooseAction = ({ route }) => {
117
113
  }, [actions, numberActionCanAdd, automateId, unitId, navigate, t]);
118
114
 
119
115
  const handleOnSelectAction = (action) => {
120
- let newActions = [...actions];
121
-
122
- if (index < 0) {
123
- newActions.push(action);
124
- } else {
125
- newActions[index] = action; // todo Bang this case is not happening?
126
- }
127
- setActions(newActions);
116
+ setActions((prevActions) => {
117
+ const index = prevActions.findIndex(
118
+ (item) => item.index === action.index
119
+ );
120
+ if (index !== -1) {
121
+ const newActions = [...prevActions];
122
+ newActions[index] = action;
123
+ return newActions;
124
+ } else {
125
+ return [...prevActions, action];
126
+ }
127
+ });
128
128
  };
129
129
 
130
130
  useEffect(() => {
@@ -141,11 +141,11 @@ const ChooseAction = ({ route }) => {
141
141
  >
142
142
  <View>
143
143
  {!!data?.length &&
144
- data.map((item) => (
144
+ data.map((item, index) => (
145
145
  <RenderActionItem
146
146
  device={device}
147
147
  item={item}
148
- key={item.id}
148
+ index={index}
149
149
  handleOnSelectAction={handleOnSelectAction}
150
150
  />
151
151
  ))}
@@ -79,14 +79,6 @@ const ChooseConfig = ({ route }) => {
79
79
  [configs]
80
80
  );
81
81
 
82
- const onPressItem = (item) => () => {
83
- navigate(Routes.SetupConfigCondition, {
84
- item,
85
- defaultCondition: conditions[item.id],
86
- closeScreen,
87
- });
88
- };
89
-
90
82
  const renderCondition = useCallback(
91
83
  (item) => {
92
84
  return generateAutomationDataConditionText(
@@ -104,29 +96,17 @@ const ChooseConfig = ({ route }) => {
104
96
  [automate?.type, conditions, t]
105
97
  );
106
98
 
107
- useEffect(() => {
108
- fetchData();
109
- }, [fetchData]);
110
-
111
- useEffect(() => {
112
- newCondition &&
113
- setConditions((prev) => ({
114
- ...prev,
115
- [newCondition.config]: newCondition,
116
- }));
117
- }, [newCondition]);
118
-
119
- return (
120
- <NewActionWrapper
121
- name={t('set_up {name}', { name: device?.name })}
122
- nextTitle={t('continue')}
123
- onNext={onSave}
124
- canNext={checkedItem?.id}
125
- >
126
- {isLoading ? (
127
- <LoadingSelectAction style={styles.container} />
128
- ) : (
129
- configs.map((item) => {
99
+ const renderConfigs = useCallback(
100
+ (configs) => {
101
+ const onPressItem = (item) => () => {
102
+ navigate(Routes.SetupConfigCondition, {
103
+ item,
104
+ defaultCondition: conditions[item.id],
105
+ closeScreen,
106
+ });
107
+ };
108
+ if (configs.length) {
109
+ return configs.map((item) => {
130
110
  const hasCondition = conditions[item.id];
131
111
  const isChecked = checkedItem?.id === item?.id;
132
112
  const isDefault = automate?.config_id === item?.id;
@@ -170,7 +150,59 @@ const ChooseConfig = ({ route }) => {
170
150
  </TouchableOpacity>
171
151
  </View>
172
152
  );
173
- })
153
+ });
154
+ } else {
155
+ return (
156
+ <View style={styles.textCenter}>
157
+ <Text type="Body" center color={Colors.Gray7}>
158
+ {t('end_device_not_support_script', {
159
+ not_support: t(automate?.type),
160
+ support:
161
+ automate?.type === AUTOMATE_TYPE.EVENT
162
+ ? t(AUTOMATE_TYPE.VALUE_CHANGE)
163
+ : t(AUTOMATE_TYPE.EVENT),
164
+ })}
165
+ </Text>
166
+ </View>
167
+ );
168
+ }
169
+ },
170
+ [
171
+ automate,
172
+ checkedItem?.id,
173
+ closeScreen,
174
+ conditions,
175
+ navigate,
176
+ onChecked,
177
+ renderCondition,
178
+ t,
179
+ valueEvaluations,
180
+ ]
181
+ );
182
+
183
+ useEffect(() => {
184
+ fetchData();
185
+ }, [fetchData]);
186
+
187
+ useEffect(() => {
188
+ newCondition &&
189
+ setConditions((prev) => ({
190
+ ...prev,
191
+ [newCondition.config]: newCondition,
192
+ }));
193
+ }, [newCondition]);
194
+
195
+ return (
196
+ <NewActionWrapper
197
+ name={t('set_up {name}', { name: device?.name })}
198
+ nextTitle={t('continue')}
199
+ onNext={onSave}
200
+ canNext={checkedItem?.id}
201
+ >
202
+ {isLoading ? (
203
+ <LoadingSelectAction style={styles.container} />
204
+ ) : (
205
+ renderConfigs(configs)
174
206
  )}
175
207
  </NewActionWrapper>
176
208
  );
@@ -10,7 +10,12 @@ import { AUTOMATE_TYPE } from '../../../configs/Constants';
10
10
 
11
11
  const SelectMonitorDevices = ({ route }) => {
12
12
  const t = useTranslations();
13
- const { automate = {}, isCreateNewAction, closeScreen } = route?.params || {};
13
+ const {
14
+ automate = {},
15
+ isCreateNewAction,
16
+ closeScreen,
17
+ unit,
18
+ } = route?.params || {};
14
19
 
15
20
  const [stations, setStations] = useState([]);
16
21
  const [listStation, setListStation] = useState([]);
@@ -26,7 +31,7 @@ const SelectMonitorDevices = ({ route }) => {
26
31
  configs.params.type = 'event';
27
32
  }
28
33
  await fetchWithCache(
29
- API.UNIT.DEVICE_SENSOR(automate?.unit),
34
+ API.UNIT.DEVICE_SENSOR(automate?.unit ? automate?.unit : unit.id),
30
35
  configs,
31
36
  (response) => {
32
37
  const { success, data } = response;
@@ -45,7 +50,7 @@ const SelectMonitorDevices = ({ route }) => {
45
50
  }
46
51
  );
47
52
  setLoading(false);
48
- }, [automate?.type, automate?.unit]);
53
+ }, [automate?.type, automate?.unit, unit.id]);
49
54
 
50
55
  useEffect(() => {
51
56
  if (!automate?.sensor_id) {
@@ -67,4 +67,10 @@ export default StyleSheet.create({
67
67
  alignItems: 'flex-start',
68
68
  paddingLeft: 20,
69
69
  },
70
+ textCenter: {
71
+ justifyContent: 'center',
72
+ alignItems: 'center',
73
+ margin: 30,
74
+ height: Constants.height * 0.7,
75
+ },
70
76
  });
@@ -294,4 +294,77 @@ describe('Test ChooseAction', () => {
294
294
  })
295
295
  );
296
296
  });
297
+
298
+ test('test press card on/off Simple Action repeatedly', async () => {
299
+ const config1 = 1;
300
+ const response = [
301
+ {
302
+ id: 1,
303
+ title: '',
304
+ template: 'OnOffSimpleActionTemplate',
305
+ configuration: {
306
+ allow_config_store_value: true,
307
+ config: config1,
308
+ action_on: '94ae262d-46e3-42ff-9d10-516831ecc830',
309
+ action_off: '94ae262d-46e3-42ff-9d10-516831ecc830',
310
+ },
311
+ },
312
+ ];
313
+ mock.onGet(API.DEVICE.DISPLAY_ACTIONS(1)).reply(200, response);
314
+ await act(async () => {
315
+ tree = await renderer.create(wrapComponent(route));
316
+ });
317
+ const instance = tree.root;
318
+
319
+ const cards = instance.findAllByType(SelectActionCard);
320
+ // force all render
321
+ await act(async () => {
322
+ cards.map((card) => card.props.onPress());
323
+ });
324
+
325
+ const simpleActionOn = instance.find(
326
+ (el) =>
327
+ el.props.accessibilityLabel ===
328
+ AccessibilityLabel.ON_OFF_SIMPLE_ACTION_ON &&
329
+ el.type === TouchableOpacity
330
+ );
331
+ await act(async () => {
332
+ await simpleActionOn.props.onPress();
333
+ });
334
+
335
+ const simpleActionOnOff = instance.find(
336
+ (el) =>
337
+ el.props.accessibilityLabel ===
338
+ AccessibilityLabel.ON_OFF_SIMPLE_ACTION_OFF &&
339
+ el.type === TouchableOpacity
340
+ );
341
+ await act(async () => {
342
+ await simpleActionOnOff.props.onPress();
343
+ });
344
+
345
+ mock.onPost(API.AUTOMATE.ADD_SCRIPT_ACTION(1)).reply(200);
346
+ const buttonSave = instance.find(
347
+ (el) =>
348
+ el.props.accessibilityLabel === AccessibilityLabel.BOTTOM_VIEW_MAIN &&
349
+ el.type === TouchableOpacity
350
+ );
351
+ await act(async () => {
352
+ await buttonSave.props.onPress();
353
+ });
354
+
355
+ expect(mock.history.post[0].data).toEqual(
356
+ JSON.stringify({
357
+ list_action: [
358
+ {
359
+ action: '94ae262d-46e3-42ff-9d10-516831ecc830',
360
+ data: {
361
+ state: 0,
362
+ config_id: config1,
363
+ config_value: 0,
364
+ },
365
+ },
366
+ ],
367
+ })
368
+ );
369
+ });
297
370
  });
@@ -157,10 +157,11 @@ const ScriptDetail = ({ route }) => {
157
157
  <TouchableOpacity
158
158
  onPress={handleShowMenuAction}
159
159
  ref={refMenuAction}
160
- style={[styles.headerButton, styles.moreButton]}
161
160
  accessibilityLabel={AccessibilityLabel.ICON_MORE}
162
161
  >
163
- <Icon name={'more'} size={27} color={Colors.Black} />
162
+ <View style={[styles.headerButton, styles.moreButton]}>
163
+ <Icon name={'more'} size={27} color={Colors.Black} />
164
+ </View>
164
165
  </TouchableOpacity>
165
166
  </View>
166
167
  ),
@@ -10,6 +10,7 @@ export default StyleSheet.create({
10
10
  marginBottom: 32,
11
11
  },
12
12
  scollView: {
13
+ marginTop: 30,
13
14
  paddingHorizontal: 16,
14
15
  paddingBottom: 150,
15
16
  },
@@ -56,6 +56,7 @@ const navContextValue = {
56
56
  isFocused: () => false,
57
57
  addListener: jest.fn(() => jest.fn()),
58
58
  };
59
+
59
60
  const wrapComponent = (state, account, route) => (
60
61
  <SCProvider initState={state}>
61
62
  <NavigationContext.Provider
@@ -16,6 +16,12 @@ import Compass from '../../../commons/Device/WindDirection/Compass';
16
16
  import DeviceAlertStatus from '../../../commons/Device/DeviceAlertStatus';
17
17
  import MockAdapter from 'axios-mock-adapter';
18
18
  import api from '../../../utils/Apis/axios';
19
+ import CurrentRainSensor from '../../../commons/Device/RainningSensor/CurrentRainSensor';
20
+
21
+ jest.mock('../../../iot/states', () => ({
22
+ useConfigGlobalState: () => [{}, null],
23
+ getConfigGlobalState: () => 'en',
24
+ }));
19
25
 
20
26
  new MockAdapter(api.axiosInstance);
21
27
 
@@ -102,6 +108,7 @@ describe('Test SensorDisplayItem', () => {
102
108
  const actionGroup = instance.findAllByType(ActionGroup);
103
109
  expect(actionGroup).toHaveLength(1);
104
110
  });
111
+
105
112
  it('render camera', async () => {
106
113
  const item = {
107
114
  id: 10452,
@@ -304,4 +311,106 @@ describe('Test SensorDisplayItem', () => {
304
311
  const flatListItems = instance.findAllByType(DeviceAlertStatus);
305
312
  expect(flatListItems).toHaveLength(1);
306
313
  });
314
+
315
+ const _testCircleMini = async (item, expectProps) => {
316
+ const sensor = {
317
+ name: 'Sensor name',
318
+ is_managed_by_backend: false,
319
+ };
320
+
321
+ await act(async () => {
322
+ tree = await renderer.create(wrapComponent({ item, sensor }));
323
+ });
324
+ const instance = tree.root;
325
+
326
+ const currentRainSensor = instance.findByType(CurrentRainSensor);
327
+ expect(currentRainSensor.props).toEqual(expectProps);
328
+ };
329
+
330
+ it('render circle_mini', async () => {
331
+ const globalStates = require('../../../iot/states');
332
+ globalStates.useConfigGlobalState = () => [{ 99: { value: 1 } }, null];
333
+
334
+ const item = {
335
+ id: 10452,
336
+ order: 0,
337
+ template: 'circle_mini',
338
+ type: 'circle_mini',
339
+ configuration: {
340
+ config: {
341
+ id: 99,
342
+ title: '1',
343
+ color: 'blue',
344
+ },
345
+ },
346
+ is_configuration_ready: true,
347
+ };
348
+
349
+ await _testCircleMini(item, {
350
+ data: [
351
+ {
352
+ id: 99,
353
+ title: '1',
354
+ color: 'blue',
355
+ value: 1,
356
+ evaluate: {
357
+ color: null,
358
+ text: '--',
359
+ },
360
+ },
361
+ ],
362
+ isWidgetOrder: undefined,
363
+ });
364
+ });
365
+
366
+ it('render circle_mini without config value', async () => {
367
+ const globalStates = require('../../../iot/states');
368
+ globalStates.useConfigGlobalState = () => [{ 2: { value: 1 } }, null];
369
+
370
+ const item = {
371
+ id: 10452,
372
+ order: 0,
373
+ template: 'circle_mini',
374
+ type: 'circle_mini',
375
+ configuration: {
376
+ config: {
377
+ id: 99,
378
+ title: '1',
379
+ color: 'blue',
380
+ },
381
+ },
382
+ is_configuration_ready: true,
383
+ };
384
+
385
+ await _testCircleMini(item, {
386
+ data: [
387
+ {
388
+ id: 99,
389
+ title: '1',
390
+ color: 'blue',
391
+ value: null,
392
+ evaluate: null,
393
+ },
394
+ ],
395
+ isWidgetOrder: undefined,
396
+ });
397
+ });
398
+
399
+ it('render circle_mini without config id', async () => {
400
+ const item = {
401
+ id: 10452,
402
+ order: 0,
403
+ template: 'circle_mini',
404
+ type: 'circle_mini',
405
+ configuration: {
406
+ config: null,
407
+ },
408
+ is_configuration_ready: true,
409
+ };
410
+
411
+ await _testCircleMini(item, {
412
+ data: undefined,
413
+ isWidgetOrder: undefined,
414
+ });
415
+ });
307
416
  });
@@ -46,6 +46,13 @@ export const SensorDisplayItem = ({
46
46
 
47
47
  const evaluateValue = useEvaluateValue();
48
48
 
49
+ const checkConfigValue = (configValue) => {
50
+ if (configValue === null || configValue === undefined) {
51
+ return false;
52
+ }
53
+ return true;
54
+ };
55
+
49
56
  const getData = useCallback(() => {
50
57
  if (!item.configuration) {
51
58
  return;
@@ -83,6 +90,34 @@ export const SensorDisplayItem = ({
83
90
  value_evaluation,
84
91
  ]);
85
92
 
93
+ const getDataCircleMini = useCallback(() => {
94
+ if (!item?.configuration?.config?.id) {
95
+ return;
96
+ }
97
+
98
+ const config = item.configuration.config;
99
+ const configValue = configValues[config.id]?.value;
100
+ const configEvaluate = evaluate[config.id] || {};
101
+
102
+ const hasConfigValue = checkConfigValue(configValue);
103
+
104
+ let value = {
105
+ id: config.id,
106
+ value: hasConfigValue ? configValue : null,
107
+ evaluate: hasConfigValue
108
+ ? evaluateValue(configValue, value_evaluation) || configEvaluate
109
+ : null,
110
+ };
111
+
112
+ return [{ ...config, ...value }].filter(Boolean);
113
+ }, [
114
+ configValues,
115
+ evaluate,
116
+ evaluateValue,
117
+ item.configuration,
118
+ value_evaluation,
119
+ ]);
120
+
86
121
  const doAction = useCallback(
87
122
  async (action, data) => {
88
123
  if (processing) {
@@ -156,6 +191,13 @@ export const SensorDisplayItem = ({
156
191
  isWidgetOrder={isWidgetOrder}
157
192
  />
158
193
  );
194
+ case 'circle_mini':
195
+ return (
196
+ <CurrentRainSensor
197
+ data={getDataCircleMini(item)}
198
+ isWidgetOrder={isWidgetOrder}
199
+ />
200
+ );
159
201
  case 'value':
160
202
  switch (type || template) {
161
203
  case 'circle':
@@ -668,20 +668,23 @@ const DeviceDetail = ({ route }) => {
668
668
  () => (
669
669
  <View style={styles.headerRight}>
670
670
  <TouchableOpacity
671
- style={styles.buttonStar}
672
671
  onPress={isFavorite ? removeFromFavorites : addToFavorites}
673
672
  accessibilityLabel={AccessibilityLabel.HEADER_DEVICE_BUTTON_STAR}
674
673
  >
675
- {isFavorite ? (
676
- <IconFill name="star" size={25} color={Colors.Yellow6} />
677
- ) : (
678
- <IconOutline name="star" size={25} />
679
- )}
674
+ <View style={styles.buttonStar}>
675
+ {isFavorite ? (
676
+ <IconFill name="star" size={25} color={Colors.Yellow6} />
677
+ ) : (
678
+ <IconOutline name="star" size={25} />
679
+ )}
680
+ </View>
680
681
  </TouchableOpacity>
681
682
 
682
683
  {isShowSetupEmergencyContact && (
683
- <TouchableOpacity style={styles.button} onPress={onPressSetting}>
684
- <Icon name="setting" size={25} color={Colors.Black} />
684
+ <TouchableOpacity onPress={onPressSetting}>
685
+ <View style={styles.button}>
686
+ <Icon name="setting" size={25} color={Colors.Black} />
687
+ </View>
685
688
  </TouchableOpacity>
686
689
  )}
687
690
 
@@ -87,6 +87,8 @@ export default StyleSheet.create({
87
87
  justifyContent: 'center',
88
88
  alignItems: 'center',
89
89
  marginRight: 14,
90
+ width: 30,
91
+ height: 30,
90
92
  },
91
93
  button: {
92
94
  marginRight: 10,
@@ -128,11 +128,12 @@ const SelectUnit = () => {
128
128
  const rightComponent = useMemo(
129
129
  () => (
130
130
  <TouchableOpacity
131
- style={styles.buttonClose}
132
131
  onPress={handleOnGoBackAndClose}
133
132
  accessibilityLabel={AccessibilityLabel.ICON_CLOSE}
134
133
  >
135
- <Icon name={'close'} size={24} color={Colors.Black} />
134
+ <View style={styles.buttonClose}>
135
+ <Icon name={'close'} size={24} color={Colors.Black} />
136
+ </View>
136
137
  </TouchableOpacity>
137
138
  ),
138
139
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -21,6 +21,6 @@ describe('Test GatewayList', () => {
21
21
  });
22
22
  const instance = tree.root;
23
23
  const Views = instance.findAllByType(View);
24
- expect(Views).toHaveLength(13);
24
+ expect(Views).toHaveLength(14);
25
25
  });
26
26
  });
@@ -11,6 +11,6 @@ describe('Test Information', () => {
11
11
  });
12
12
  const instance = tree.root;
13
13
  const Views = instance.findAllByType(View);
14
- expect(Views).toHaveLength(14);
14
+ expect(Views).toHaveLength(15);
15
15
  });
16
16
  });
@@ -1037,6 +1037,9 @@ export default {
1037
1037
  update_smart: 'Update Smart',
1038
1038
  automate: 'Automate',
1039
1039
  smart: 'Smart',
1040
+ end_device_not_support_script:
1041
+ // eslint-disable-next-line max-len
1042
+ 'This device does not support installing "{not_support}" scripts, but you can use "{support}" scripts to perform the desired setup.',
1040
1043
  smart_account: 'Smart account',
1041
1044
  delete_smart_account: 'Delete smart account',
1042
1045
  this_action_will_remove_all_related_devices:
@@ -1052,6 +1052,9 @@ export default {
1052
1052
  smart: 'Thông minh',
1053
1053
  smart_account: 'Tài khoản thông minh',
1054
1054
  delete_smart_account: 'Xóa tài khoản thông minh',
1055
+ end_device_not_support_script:
1056
+ // eslint-disable-next-line max-len
1057
+ 'Thiết bị này không hỗ trợ việc cài đặt kịch bản "{not_support}", nhưng bạn có thể sử dụng kịch bản "{support}" để thực hiện việc cài đặt mong muốn.',
1055
1058
  this_action_will_remove_all_related_devices:
1056
1059
  'Hành động này sẽ xóa tất cả các thiết bị liên quan và nó sẽ cài đặt vĩnh viễn. Bạn có muốn tiếp tục không?',
1057
1060
  edit_device: 'Chỉnh sửa thiết bị',