@eohjsc/react-native-smart-city 0.3.29 → 0.3.30

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 (39) hide show
  1. package/package.json +1 -1
  2. package/src/commons/Action/ItemQuickAction.js +1 -0
  3. package/src/commons/ActionGroup/SliderRangeTemplate.js +22 -14
  4. package/src/commons/BottomButtonView/index.js +1 -0
  5. package/src/commons/Button/index.js +2 -0
  6. package/src/commons/Device/ItemAddNew/index.js +5 -1
  7. package/src/commons/Device/ItemDevice.js +11 -9
  8. package/src/commons/MenuActionMore/index.js +1 -1
  9. package/src/commons/NavBar/index.js +2 -1
  10. package/src/commons/Popover/index.js +7 -6
  11. package/src/commons/SubUnit/OneTap/ItemOneTap.js +4 -1
  12. package/src/configs/Constants.js +11 -0
  13. package/src/hooks/Common/useDevicesStatus.js +1 -1
  14. package/src/hooks/IoT/useValueEvaluation.js +10 -19
  15. package/src/iot/RemoteControl/GoogleHome.js +6 -6
  16. package/src/navigations/UnitStack.js +5 -8
  17. package/src/screens/AddNewAction/Device/index.js +5 -1
  18. package/src/screens/AddNewAction/SelectAction.js +30 -14
  19. package/src/screens/AddNewAction/__test__/SelectAction.test.js +1 -0
  20. package/src/screens/AddNewAutoSmart/index.js +2 -0
  21. package/src/screens/AddNewGateway/PlugAndPlay/GatewayWifiList.js +13 -1
  22. package/src/screens/Device/components/SensorConnectStatusViewHeader.js +10 -11
  23. package/src/screens/Device/detail.js +35 -16
  24. package/src/screens/Device/hooks/useFavoriteDevice.js +4 -2
  25. package/src/screens/EmergencyContacts/EmergencyContactsAddNew.js +3 -3
  26. package/src/screens/EmergencyContacts/EmergencyContactsSelectContacts.js +4 -7
  27. package/src/screens/Notification/__test__/NotificationItem.test.js +6 -30
  28. package/src/screens/Notification/components/NotificationItem.js +3 -19
  29. package/src/screens/ScriptDetail/__test__/index.test.js +1 -1
  30. package/src/screens/ScriptDetail/index.js +2 -1
  31. package/src/screens/Sharing/Components/SensorItem.js +4 -1
  32. package/src/screens/Sharing/Components/Styles/SensorItemStyles.js +4 -0
  33. package/src/screens/Sharing/Components/Styles/TitleCheckBoxStyles.js +4 -0
  34. package/src/screens/Sharing/Components/TitleCheckBox.js +17 -8
  35. package/src/screens/Unit/Detail.js +6 -1
  36. package/src/screens/Unit/SelectAddToFavorites.js +11 -1
  37. package/src/screens/Unit/components/AutomateScript/index.js +5 -2
  38. package/src/utils/I18n/translations/en.json +5 -0
  39. package/src/utils/I18n/translations/vi.json +5 -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.3.29",
4
+ "version": "0.3.30",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -88,6 +88,7 @@ const ItemQuickAction = memo(({ sensor, wrapperStyle, setStatus }) => {
88
88
  return (
89
89
  <TouchableOpacity
90
90
  testID={TESTID.ITEM_QUICK_ACTION_PRESS}
91
+ accessibilityLabel={`${TESTID.ITEM_QUICK_ACTION_PRESS}-${sensor?.id}`}
91
92
  onPress={onActionPress}
92
93
  >
93
94
  <View style={wrapperStyle}>
@@ -1,4 +1,4 @@
1
- import React, { memo, useCallback, useState, useMemo, useEffect } from 'react';
1
+ import React, { memo, useCallback, useState, useEffect } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import { useTranslations } from '../../hooks/Common/useTranslations';
4
4
  import styles from './SliderRangeTemplateStyles';
@@ -13,30 +13,38 @@ const SliderRangeTemplate = memo(({ actionGroup, doAction, sensor }) => {
13
13
  const { configuration } = actionGroup;
14
14
  const [valueBrightness, setValueBrightness] = useState(0);
15
15
  const [valueBrightnessTemp, setValueBrightnessTemp] = useState(0);
16
+ const [isFirstTime, setIsFirstTime] = useState(true);
17
+
16
18
  const [configValues] = useConfigGlobalState('configValues');
17
19
 
18
20
  const onChangeBrightness = useCallback(
19
- (value) => {
20
- doAction(
21
+ async (value) => {
22
+ await doAction(
21
23
  configuration?.action_brightness_data,
22
24
  JSON.stringify({ value_brness: value })
23
25
  );
26
+ await setValueBrightness(value);
27
+ await setValueBrightnessTemp(value);
24
28
  },
25
29
  [configuration?.action_brightness_data, doAction]
26
30
  );
27
31
 
28
- const percentBrightness = useMemo(() => {
29
- return valueBrightnessTemp || valueBrightness || 0;
30
- }, [valueBrightness, valueBrightnessTemp]);
31
-
32
32
  useEffect(() => {
33
33
  const { config } = configuration;
34
34
  const configValue = configValues[config];
35
- let valueBrness = configValue?.value;
36
- if (valueBrness >= 0 && valueBrightness >= 0) {
37
- setValueBrightness(valueBrness);
35
+ if (configValue?.value >= 0 && isFirstTime) {
36
+ setValueBrightness(configValue?.value);
37
+ setIsFirstTime(false);
38
+ } else if (configValue?.value === valueBrightnessTemp) {
39
+ setValueBrightness(valueBrightnessTemp);
38
40
  }
39
- }, [configuration.config, configValues, configuration, valueBrightness]);
41
+ }, [
42
+ configuration.config,
43
+ configValues,
44
+ configuration,
45
+ isFirstTime,
46
+ valueBrightnessTemp,
47
+ ]);
40
48
 
41
49
  return (
42
50
  <View style={styles.viewBrightness}>
@@ -49,9 +57,9 @@ const SliderRangeTemplate = memo(({ actionGroup, doAction, sensor }) => {
49
57
  <View style={styles.RightBrightness}>
50
58
  <View style={styles.slider}>
51
59
  <SliderRange
52
- value={valueBrightnessTemp}
60
+ value={valueBrightness}
53
61
  onSlidingComplete={onChangeBrightness}
54
- onValueChange={setValueBrightnessTemp}
62
+ onValueChange={setValueBrightness}
55
63
  step={1}
56
64
  minimumValue={0}
57
65
  maximumValue={100}
@@ -64,7 +72,7 @@ const SliderRangeTemplate = memo(({ actionGroup, doAction, sensor }) => {
64
72
  </View>
65
73
  <View style={styles.valuePercent}>
66
74
  <Text type="Label" style={styles.textValuePercent}>
67
- {`${percentBrightness}%`}
75
+ {`${valueBrightness}%`}
68
76
  </Text>
69
77
  </View>
70
78
  </View>
@@ -42,6 +42,7 @@ const BottomButtonView = memo(
42
42
  textType={textTypeMain}
43
43
  style={rowButton && styleCustom.buttonMainRow}
44
44
  testID={`${testIDPrefix}${TESTID.BOTTOM_VIEW_MAIN}`}
45
+ accessibilityLabel={TESTID.BOTTOM_VIEW_MAIN}
45
46
  />
46
47
  )}
47
48
  {secondaryTitle && (
@@ -133,6 +133,7 @@ export default ({
133
133
  textSemiBold = true,
134
134
  style,
135
135
  testID,
136
+ accessibilityLabel,
136
137
  }) => {
137
138
  const styleButton = ButtonStyle[type];
138
139
  const textColor = TextColor[type];
@@ -158,6 +159,7 @@ export default ({
158
159
  onPress={onPress}
159
160
  disabled={isDisabled}
160
161
  activeOpacity={activeOpacity}
162
+ accessibilityLabel={accessibilityLabel}
161
163
  >
162
164
  <View style={styles.wrap}>
163
165
  {icon}
@@ -9,7 +9,11 @@ import { TESTID } from '../../../configs/Constants';
9
9
 
10
10
  const ItemAddNew = memo(({ title, onAddNew, wrapStyle }) => {
11
11
  return (
12
- <TouchableWithoutFeedback onPress={onAddNew}>
12
+ <TouchableWithoutFeedback
13
+ onPress={onAddNew}
14
+ testID={TESTID.TOUCH_ADD_NEW_FAVORITES}
15
+ accessibilityLabel={TESTID.TOUCH_ADD_NEW_FAVORITES}
16
+ >
13
17
  <View style={[styles.container, wrapStyle]}>
14
18
  <View style={styles.boxIcon}>
15
19
  <TouchableOpacity
@@ -61,17 +61,18 @@ const ItemDevice = memo(
61
61
 
62
62
  const borderColor = (() => {
63
63
  if (!!sensor && sensor?.is_managed_by_backend) {
64
+ if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
65
+ return Colors.Gray4;
66
+ }
64
67
  if (isConnectedViaBLE) {
65
68
  return Colors.Gray4;
66
69
  }
67
70
  if (isFetchingStatus || isConnectedViaInternet) {
68
71
  return Colors.Gray4;
69
72
  }
73
+ return Colors.Red6;
70
74
  }
71
75
  // not managed by backend
72
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
73
- return Colors.Gray4;
74
- }
75
76
  if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
76
77
  if (isGGHomeConnecting || isGGHomeConnected) {
77
78
  return Colors.Gray4;
@@ -82,6 +83,9 @@ const ItemDevice = memo(
82
83
 
83
84
  const textConnected = (() => {
84
85
  if (!!sensor && sensor?.is_managed_by_backend) {
86
+ if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
87
+ return t('connected');
88
+ }
85
89
  if (isConnectedViaBLE) {
86
90
  return t('connected');
87
91
  }
@@ -91,11 +95,9 @@ const ItemDevice = memo(
91
95
  if (isConnectedViaInternet) {
92
96
  return t('connected');
93
97
  }
98
+ return t('disconnected');
94
99
  }
95
100
  // not managed by backend
96
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
97
- return t('connected');
98
- }
99
101
  if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
100
102
  if (isGGHomeConnecting) {
101
103
  return t('connecting');
@@ -109,6 +111,9 @@ const ItemDevice = memo(
109
111
 
110
112
  const canRenderQuickAction = (() => {
111
113
  if (!!sensor && sensor?.is_managed_by_backend) {
114
+ if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
115
+ return true;
116
+ }
112
117
  if (isConnectedViaBLE) {
113
118
  return true;
114
119
  }
@@ -118,9 +123,6 @@ const ItemDevice = memo(
118
123
  return true;
119
124
  }
120
125
  // not managed by backend
121
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
122
- return true;
123
- }
124
126
  if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
125
127
  return !isGGHomeConnecting;
126
128
  }
@@ -67,7 +67,7 @@ const MenuActionMore = memo(
67
67
  key={index}
68
68
  testID={TESTID.TOUCHABLE_ACTION_ADD_MORE}
69
69
  accessibilityLabel={`${idLabelItem}-${
70
- item?.route ? item?.id : item?.station?.id
70
+ (item?.route ? item?.id : item?.station?.id) || index
71
71
  }`}
72
72
  disabled={isDisable}
73
73
  >
@@ -18,6 +18,7 @@ const NavBar = memo(
18
18
  style,
19
19
  idLabelScrollView,
20
20
  idLabelItem,
21
+ idLabelIconBars,
21
22
  }) => {
22
23
  const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
23
24
  usePopover();
@@ -39,7 +40,7 @@ const NavBar = memo(
39
40
  onPress={handleShowMenuAction}
40
41
  ref={refMenuAction}
41
42
  testID={TESTID.NAVBAR_ICON_BARS}
42
- accessibilityLabel={TESTID.NAVBAR_ICON_BARS}
43
+ accessibilityLabel={idLabelIconBars}
43
44
  >
44
45
  <Icon name={'bars'} size={19} color={Colors.Black} />
45
46
  </TouchableOpacity>
@@ -3,22 +3,23 @@ import Popover from 'react-native-popover-view';
3
3
  import { SCContext } from '../../context';
4
4
  import { Action } from '../../context/actionType';
5
5
 
6
- const PopoverComponent = (props) => {
6
+ const PopoverComponent = ({ onRequestClose, ...restProps }) => {
7
7
  const { setAction } = useContext(SCContext);
8
8
 
9
- const onCloseStart = () => {
9
+ const _onRequestClose = () => {
10
10
  setAction(Action.SET_POPOVER_ANIMATING, true);
11
+ onRequestClose && onRequestClose();
11
12
  };
12
13
 
13
- const onCloseComplete = () => {
14
+ const _onCloseComplete = () => {
14
15
  setAction(Action.SET_POPOVER_ANIMATING, false);
15
16
  };
16
17
 
17
18
  return (
18
19
  <Popover
19
- onCloseStart={onCloseStart}
20
- onCloseComplete={onCloseComplete}
21
- {...props}
20
+ onRequestClose={_onRequestClose}
21
+ onCloseComplete={_onCloseComplete}
22
+ {...restProps}
22
23
  />
23
24
  );
24
25
  };
@@ -103,7 +103,10 @@ const ItemOneTap = memo(
103
103
  ? timeDifference(new Date(), moment(activate_at), true)
104
104
  : null;
105
105
  return (
106
- <TouchableWithoutFeedback onPress={onPressItem || goToDetail}>
106
+ <TouchableWithoutFeedback
107
+ onPress={onPressItem || goToDetail}
108
+ accessibilityLabel={`${TESTID.AUTOMATE_SCRIPT_NAME}-${id}`}
109
+ >
107
110
  <View style={[styles.container, wrapSyles]}>
108
111
  <View style={styles.boxIcon}>
109
112
  {displayIcon()}
@@ -313,11 +313,13 @@ export const TESTID = {
313
313
  SUB_UNIT_GO_DETAIL: 'SUB_UNIT_GO_DETAIL',
314
314
  VIEW_SUB_UNIT_AUTOMATE: 'VIEW_SUB_UNIT_AUTOMATE',
315
315
  SUB_UNIT_NAME: 'SUB_UNIT_NAME',
316
+ SUB_UNIT_FAVORITES: 'SUB_UNIT_FAVORITES',
316
317
  ANIMATED_SCROLL: 'ANIMATED_SCROLL',
317
318
  ADD_SUB_UNIT: 'ADD_SUB_UNIT',
318
319
 
319
320
  // NavBar
320
321
  NAVBAR_ICON_BARS: 'NAVBAR_ICON_BARS',
322
+ NAVBAR_ICON_BARS_ADD_FAVORITES: 'NAVBAR_ICON_BARS_ADD_FAVORITES',
321
323
  NAVBAR_MENU_ACTION_MORE: 'NAVBAR_MENU_ACTION_MORE',
322
324
  NAVBAR_ON_SNAP_ITEM: 'NAVBAR_ON_SNAP_ITEM',
323
325
 
@@ -427,6 +429,7 @@ export const TESTID = {
427
429
 
428
430
  // Automate
429
431
  AUTOMATE_SCRIPT_ACTION: 'AUTOMATE_SCRIPT_ACTION',
432
+ AUTOMATE_SCRIPT_NAME: 'AUTOMATE_SCRIPT_NAME',
430
433
  NAME_YOUR_BUTTON: 'NAME_YOUR_BUTTON',
431
434
  BUTTON_ACTIVATE_ONE_TAP: 'BUTTON_ACTIVATE_ONE_TAP',
432
435
  BUTTON_EDIT_SCRIPT_ACTION: 'BUTTON_EDIT_SCRIPT_ACTION',
@@ -444,6 +447,7 @@ export const TESTID = {
444
447
 
445
448
  // Header Device
446
449
  HEADER_DEVICE_BUTTON_STAR: 'HEADER_DEVICE_BUTTON_STAR',
450
+ HEADER_SCRIPT_DETAIL_BUTTON_STAR: 'HEADER_SCRIPT_DETAIL_BUTTON_STAR',
447
451
  HEADER_DEVICE_BUTTON_MORE: 'HEADER_DEVICE_BUTTON_MORE',
448
452
 
449
453
  // EMERGENCY BUTTON
@@ -666,6 +670,13 @@ export const TESTID = {
666
670
  ADD_NEW_DEVICE_ADD: 'ADD_NEW_DEVICE_ADD',
667
671
  ADD_NEW_DEVICE_THEN_SELECT: 'ADD_NEW_DEVICE_THEN_SELECT',
668
672
 
673
+ // Add Favorites
674
+ TOUCH_ADD_NEW_FAVORITES: 'TOUCH_ADD_NEW_FAVORITES',
675
+ LIST_FAVORITES: 'LIST_FAVORITES',
676
+ TOUCHABLE_ACTION_ADD_ITEM_FAVORITE: 'TOUCHABLE_ACTION_ADD_ITEM_FAVORITE',
677
+ TOUCHABLE_ACTION_ADD_ITEM_AUTOMATE_FAVORITE:
678
+ 'TOUCHABLE_ACTION_ADD_ITEM_AUTOMATE_FAVORITE',
679
+
669
680
  // Setup gateway wifi
670
681
  SETUP_GATEWAY_WIFI_TITLE: 'SETUP_GATEWAY_WIFI_TITLE',
671
682
  SETUP_GATEWAY_WIFI_PLEASE_SELECT_A_WIFI: 'SETUP_GATEWAY_PLEASE_SELECT_A_WIFI',
@@ -27,7 +27,7 @@ const useDevicesStatus = (unit, devices) => {
27
27
  if (success) {
28
28
  setAction(Action.SET_DEVICES_STATUS, data);
29
29
  }
30
- timeoutId = setTimeout(() => getDevicesStatus(_unit, _devices), 5000);
30
+ timeoutId = setTimeout(() => getDevicesStatus(_unit, _devices), 10000);
31
31
  // eslint-disable-next-line react-hooks/exhaustive-deps
32
32
  }, []);
33
33
 
@@ -1,4 +1,4 @@
1
- import { useCallback, useContext, useEffect, useState } from 'react';
1
+ import { useCallback, useContext, useEffect } from 'react';
2
2
  import { API } from '../../configs';
3
3
  import { SCContext, useSCContextSelector } from '../../context';
4
4
  import { Action } from '../../context/actionType';
@@ -6,14 +6,15 @@ import { axiosGet } from '../../utils/Apis/axios';
6
6
 
7
7
  const useValueEvaluations = (unitId) => {
8
8
  const { setAction } = useContext(SCContext);
9
- const [fetching, setFetching] = useState(false);
9
+ const fetchedValueEvaluationUnits = useSCContextSelector((state) => {
10
+ return state.fetchedValueEvaluationUnits || [];
11
+ });
10
12
 
11
13
  const fetchConfigValueEvaluations = useCallback(
12
14
  async (page = 1) => {
13
- if (!unitId) {
15
+ if (!unitId || fetchedValueEvaluationUnits.indexOf(unitId) !== -1) {
14
16
  return;
15
17
  }
16
- setFetching(true);
17
18
  const params = new URLSearchParams();
18
19
  params.append('config__end_device__station__unit', unitId);
19
20
  params.append('page', page);
@@ -34,25 +35,15 @@ const useValueEvaluations = (unitId) => {
34
35
  data: [],
35
36
  });
36
37
  }
37
- setFetching(false);
38
38
  },
39
- [unitId, setAction]
39
+ // eslint-disable-next-line react-hooks/exhaustive-deps
40
+ [unitId, fetchedValueEvaluationUnits]
40
41
  );
41
42
 
42
- const fetchedValueEvaluationUnits = useSCContextSelector((state) => {
43
- return state.fetchedValueEvaluationUnits || [];
44
- });
45
-
46
43
  useEffect(() => {
47
- if (!fetching && !(fetchedValueEvaluationUnits.indexOf(unitId) !== -1)) {
48
- fetchConfigValueEvaluations();
49
- }
50
- }, [
51
- unitId,
52
- fetching,
53
- fetchConfigValueEvaluations,
54
- fetchedValueEvaluationUnits,
55
- ]);
44
+ fetchConfigValueEvaluations();
45
+ // eslint-disable-next-line react-hooks/exhaustive-deps
46
+ }, [unitId]);
56
47
  };
57
48
 
58
49
  export default useValueEvaluations;
@@ -127,12 +127,6 @@ export const googleHomeConnect = async (
127
127
  for (let i = 0; i < options.length; i++) {
128
128
  const option = options[i];
129
129
 
130
- if (option.chip_id in oldConnections && !!oldConnections[option.chip_id]) {
131
- connections[option.chip_id] = oldConnections[option.chip_id];
132
- continue;
133
- }
134
- connections[option.chip_id] = 0; // connecting
135
-
136
130
  option.config_maps.forEach((configMap) => {
137
131
  if (option.text_maps) {
138
132
  textMaps[configMap.entity_id] = option.text_maps;
@@ -156,6 +150,12 @@ export const googleHomeConnect = async (
156
150
  }
157
151
  });
158
152
 
153
+ if (option.chip_id in oldConnections && !!oldConnections[option.chip_id]) {
154
+ connections[option.chip_id] = oldConnections[option.chip_id];
155
+ continue;
156
+ }
157
+ connections[option.chip_id] = 0; // connecting
158
+
159
159
  try {
160
160
  let auth = new Auth(option.auth);
161
161
  const connection = await createConnection({ auth });
@@ -68,7 +68,7 @@ export const UnitStack = memo((props) => {
68
68
  const t = useTranslations();
69
69
  const { setAction } = useContext(SCContext);
70
70
  const { route } = props;
71
- const { unitId, unitData } = route.params.params;
71
+ const { unitId, unitData, isSuccessfullyConnected } = route.params.params;
72
72
 
73
73
  useEffect(() => {
74
74
  const unsubscribe = NetInfo.addEventListener((state) => {
@@ -129,15 +129,12 @@ export const UnitStack = memo((props) => {
129
129
  }, []);
130
130
 
131
131
  useEffect(() => {
132
- return () => {
133
- const id = unitId || unitData?.id;
134
- if (!id) {
135
- return;
136
- }
132
+ const id = unitId || unitData?.id;
133
+ if (id && isSuccessfullyConnected) {
137
134
  setAction(Action.NEED_UPDATE_VALUE_EVALUATIONS, id);
138
- };
135
+ }
139
136
  // eslint-disable-next-line react-hooks/exhaustive-deps
140
- }, []);
137
+ }, [isSuccessfullyConnected]);
141
138
 
142
139
  return (
143
140
  <Stack.Navigator
@@ -3,6 +3,7 @@ import { TouchableWithoutFeedback, View } from 'react-native';
3
3
  import { Colors } from '../../../configs';
4
4
  import Text from '../../../commons/Text';
5
5
  import IconComponent from '../../../commons/IconComponent';
6
+ import { TESTID } from '../../../configs/Constants';
6
7
  import styles from './DeviceStyles';
7
8
 
8
9
  const Device = memo(({ svgMain, sensor, title, isSelectDevice, onPress }) => {
@@ -13,7 +14,10 @@ const Device = memo(({ svgMain, sensor, title, isSelectDevice, onPress }) => {
13
14
  };
14
15
 
15
16
  return (
16
- <TouchableWithoutFeedback onPress={onPressDevice}>
17
+ <TouchableWithoutFeedback
18
+ onPress={onPressDevice}
19
+ accessibilityLabel={`${TESTID.TOUCHABLE_ACTION_ADD_ITEM_FAVORITE}-${sensor.id}`}
20
+ >
17
21
  <View style={[styles.container, isActive]}>
18
22
  <View style={styles.boxIcon}>
19
23
  <IconComponent
@@ -10,7 +10,7 @@ import ActionTemplate from '../../commons/ActionTemplate';
10
10
  import NumberUpDownActionTemplate from '../../commons/OneTapTemplate/NumberUpDownActionTemplate';
11
11
  import OptionsDropdownActionTemplate from '../../commons/OneTapTemplate/OptionsDropdownActionTemplate';
12
12
  import StatesGridActionTemplate from '../../commons/OneTapTemplate/StatesGridActionTemplate';
13
- import { axiosGet, axiosPost } from '../../utils/Apis/axios';
13
+ import { axiosGet, axiosPost, axiosPut } from '../../utils/Apis/axios';
14
14
  import { API, Images } from '../../configs';
15
15
  import {
16
16
  CONDITION_TYPES,
@@ -159,19 +159,35 @@ const SelectAction = memo(({ route }) => {
159
159
  await checkConditionToContinue();
160
160
  } else {
161
161
  if (automateId) {
162
- let list_action = [...actions];
163
- list_action = list_action.map((item) => ({
164
- action: item.action,
165
- data: item.data,
166
- }));
167
- const { success } = await axiosPost(
168
- API.AUTOMATE.ADD_SCRIPT_ACTION(automateId),
169
- {
170
- list_action,
171
- unit: unit.id,
172
- }
173
- );
174
- success &&
162
+ let result = false;
163
+ if (isCreateNewAction) {
164
+ let list_action = [...actions];
165
+ list_action = list_action.map((item) => ({
166
+ action: item.action,
167
+ data: item.data,
168
+ }));
169
+ const { success } = await axiosPost(
170
+ API.AUTOMATE.ADD_SCRIPT_ACTION(automateId),
171
+ {
172
+ list_action,
173
+ unit: unit.id,
174
+ }
175
+ );
176
+ result = success;
177
+ } else {
178
+ const params = {
179
+ unit: isMultiUnits ? null : unit.id,
180
+ type: type,
181
+ name: scriptName,
182
+ action: actions[0].action,
183
+ };
184
+ const { success } = await axiosPut(
185
+ API.AUTOMATE.UPDATE_AUTOMATE(automateId),
186
+ params
187
+ );
188
+ result = success;
189
+ }
190
+ result &&
175
191
  navigate(Routes.ScriptDetail, {
176
192
  id: automateId,
177
193
  name: scriptName,
@@ -44,6 +44,7 @@ describe('Test SelectAction', () => {
44
44
  stationName: 'stationName',
45
45
  automateId: 1,
46
46
  scriptName: 'scriptName test',
47
+ isCreateNewAction: true,
47
48
  },
48
49
  };
49
50
 
@@ -78,9 +78,11 @@ const AddNewAutoSmart = memo(({ route }) => {
78
78
  },
79
79
  ],
80
80
  };
81
+
81
82
  const [data] = useState(
82
83
  automate?.id ? typeAutoSmart[AUTOMATE_TYPE.AUTOMATE] : typeAutoSmart[type]
83
84
  );
85
+
84
86
  const [selectedIndex, setSelectedIndex] = useState(
85
87
  automate?.id ? data.findIndex((obj) => obj.type === type) : -1
86
88
  );
@@ -4,6 +4,7 @@ import React, {
4
4
  useCallback,
5
5
  useContext,
6
6
  useEffect,
7
+ useRef,
7
8
  } from 'react';
8
9
  import {
9
10
  View,
@@ -45,6 +46,7 @@ const GatewayWifiList = memo(({ route }) => {
45
46
  const [password, setPassword] = useState('');
46
47
  const [selectedWifi, setSelectedWifi] = useState('');
47
48
  const [isSendWifi, setIsSendWifi] = useState(false);
49
+ const isCheckSocketOnOneTime = useRef(true);
48
50
  const { setAction } = useContext(SCContext);
49
51
 
50
52
  const isConnectWifiGateway = useSCContextSelector(
@@ -106,18 +108,26 @@ const GatewayWifiList = memo(({ route }) => {
106
108
  }
107
109
  },
108
110
  () => {
109
- ToastBottomHelper.error('Cannot get current SSID!');
111
+ ToastBottomHelper.error(t('can_not_login_to_current_ssid'));
110
112
  }
111
113
  );
112
114
  }, 3000);
113
115
  }
116
+ if (isCheckSocketOnOneTime.current && data.status === 'error') {
117
+ isCheckSocketOnOneTime.current = false;
118
+ setAction(Action.IS_CONNECT_WIFI_GATEWAY, false);
119
+ setIsShowPopupPassword(false);
120
+ ToastBottomHelper.error(t('confirm_password_not_match'));
121
+ }
114
122
  },
115
123
  [
116
124
  chip_id,
117
125
  devicePrefixName,
126
+ isCheckSocketOnOneTime,
118
127
  navigate,
119
128
  scan_sensor_data,
120
129
  setAction,
130
+ t,
121
131
  unit_id,
122
132
  unit_name,
123
133
  wifi_pass,
@@ -160,6 +170,7 @@ const GatewayWifiList = memo(({ route }) => {
160
170
  goBack();
161
171
  });
162
172
 
173
+ isCheckSocketOnOneTime.current = true;
163
174
  sendConnect(0);
164
175
  setAction(Action.IS_CONNECT_WIFI_GATEWAY, true);
165
176
  setIsSendWifi(true);
@@ -168,6 +179,7 @@ const GatewayWifiList = memo(({ route }) => {
168
179
  goBack,
169
180
  handleSocketOnMsg,
170
181
  isConnectWifiGateway,
182
+ isCheckSocketOnOneTime,
171
183
  sendConnect,
172
184
  setAction,
173
185
  socket,
@@ -18,9 +18,7 @@ export const SensorConnectStatusViewHeader = ({
18
18
  if (!!sensor && sensor?.is_managed_by_backend) {
19
19
  return false;
20
20
  } else {
21
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
22
- return false;
23
- } else if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
21
+ if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
24
22
  return isGGHomeConnecting;
25
23
  } else {
26
24
  return false;
@@ -33,6 +31,14 @@ export const SensorConnectStatusViewHeader = ({
33
31
  }
34
32
 
35
33
  if (!!sensor && sensor?.is_managed_by_backend) {
34
+ if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
35
+ return (
36
+ <>
37
+ <ConnectedViewHeader lastUpdated={lastUpdated} />
38
+ {children}
39
+ </>
40
+ );
41
+ }
36
42
  if (connectedViaNetwork) {
37
43
  return (
38
44
  <>
@@ -63,14 +69,7 @@ export const SensorConnectStatusViewHeader = ({
63
69
  }
64
70
  } else {
65
71
  // not managed by backend
66
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
67
- return (
68
- <>
69
- <ConnectedViewHeader lastUpdated={lastUpdated} />
70
- {children}
71
- </>
72
- );
73
- } else if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
72
+ if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
74
73
  if (connectedViaGGHome) {
75
74
  return (
76
75
  <>
@@ -4,6 +4,7 @@ import React, {
4
4
  useMemo,
5
5
  useState,
6
6
  useRef,
7
+ useContext,
7
8
  } from 'react';
8
9
  import { View, TouchableOpacity } from 'react-native';
9
10
  import { useTranslations } from '../../hooks/Common/useTranslations';
@@ -34,6 +35,8 @@ import BottomButtonView from '../../commons/BottomButtonView';
34
35
  import Text from '../../commons/Text';
35
36
  import { AlertAction, ButtonPopup, MenuActionMore } from '../../commons';
36
37
  import { DEVICE_TYPE, TESTID } from '../../configs/Constants';
38
+ import { SCContext } from '../../context';
39
+ import { Action } from '../../context/actionType';
37
40
 
38
41
  import { usePopover } from '../../hooks/Common';
39
42
  import { useConfigGlobalState } from '../../iot/states';
@@ -60,14 +63,17 @@ const DeviceDetail = ({ route }) => {
60
63
  const t = useTranslations();
61
64
  const navigation = useNavigation();
62
65
  const token = useSCContextSelector((state) => state.auth.account.token);
66
+ const { setAction } = useContext(SCContext);
63
67
  const [offsetTitle, setOffsetTitle] = useState(1);
64
68
  const [display, setDisplay] = useState({ items: [] });
65
- const [displayValues, setDisplayValues] = useState([]);
69
+ const [displayValuesData, setDisplayValuesData] = useState({
70
+ configs: [],
71
+ isConnected: false,
72
+ lastUpdated: null,
73
+ });
66
74
  const [controlOptions, setControlOptions] = useState({
67
75
  internet: {},
68
76
  });
69
- const [isConnected, setConnected] = useState(false);
70
- const [lastUpdated, setLastUpdated] = useState(null);
71
77
  const [lastEvent, setLastEvent] = useState({ id: 0, reportedAt: 0 });
72
78
  const [maxValue, setMaxValue] = useState(60);
73
79
  // eslint-disable-next-line no-unused-vars
@@ -447,7 +453,9 @@ const DeviceDetail = ({ route }) => {
447
453
 
448
454
  const data = item.configuration.configs.map((config) => {
449
455
  const configValue = configValues[config.id]?.value;
450
- const displayValue = displayValues.find((k) => k.id === config.id);
456
+ const displayValue = (displayValuesData.configs || []).find(
457
+ (k) => k.id === config.id
458
+ );
451
459
  if (
452
460
  (configValue === null || configValue === undefined) &&
453
461
  !displayValue
@@ -466,7 +474,7 @@ const DeviceDetail = ({ route }) => {
466
474
  });
467
475
  return data.filter((value) => value);
468
476
  },
469
- [configValues, displayValues, evaluateValue]
477
+ [configValues, displayValuesData, evaluateValue]
470
478
  );
471
479
 
472
480
  useEffect(() => {
@@ -501,17 +509,23 @@ const DeviceDetail = ({ route }) => {
501
509
  }
502
510
  );
503
511
  if (success) {
504
- setDisplayValues(data.configs);
505
- setConnected(data.is_connected);
506
- data.last_updated = data.last_updated
512
+ data.isConnected = data.is_connected;
513
+ data.lastUpdated = data.last_updated
507
514
  ? moment(data.last_updated)
508
515
  : data.last_updated;
509
-
510
- setLastUpdated(data.last_updated);
516
+ setDisplayValuesData((prevState) => {
517
+ if (prevState.isConnected !== data.isConnected) {
518
+ setAction(Action.SET_DEVICES_STATUS, [
519
+ { id: sensor?.id, is_connected: data.is_connected },
520
+ ]);
521
+ }
522
+ return data;
523
+ });
511
524
  } else if (resp_status >= 500) {
512
525
  setServerDown(true);
513
526
  }
514
- setLoading((preState) => ({ ...preState, isConnected: false }));
527
+ loading.isConnected &&
528
+ setLoading((preState) => ({ ...preState, isConnected: false }));
515
529
  };
516
530
  if (
517
531
  sensor?.is_managed_by_backend &&
@@ -524,7 +538,8 @@ const DeviceDetail = ({ route }) => {
524
538
  Object.keys(sensor).length > 1 &&
525
539
  setLoading((preState) => ({ ...preState, isConnected: false }));
526
540
  }
527
- }, [sensor, display]);
541
+ // eslint-disable-next-line react-hooks/exhaustive-deps
542
+ }, [sensor, display, loading.isConnected]);
528
543
 
529
544
  const isShowEmergencyResolve =
530
545
  display.items.filter(
@@ -563,14 +578,17 @@ const DeviceDetail = ({ route }) => {
563
578
  return (
564
579
  <SensorConnectStatusViewHeader
565
580
  sensor={sensor}
566
- connectedViaNetwork={isNetworkConnected && isConnected}
581
+ connectedViaNetwork={
582
+ isNetworkConnected && displayValuesData.isConnected
583
+ }
567
584
  connectedViaBle={
568
- (!isNetworkConnected || (isNetworkConnected && !isConnected)) &&
585
+ (!isNetworkConnected ||
586
+ (isNetworkConnected && !displayValuesData.isConnected)) &&
569
587
  isDeviceConnectedViaBle
570
588
  }
571
589
  connectedViaGGHome={isGGHomeConnected}
572
590
  isGGHomeConnecting={isGGHomeConnecting}
573
- lastUpdated={lastUpdated}
591
+ lastUpdated={displayValuesData.lastUpdated}
574
592
  isDisplayTime={isShowSetupEmergencyContact ? false : isDisplayTime}
575
593
  showWindDirection={showWindDirection}
576
594
  isGGHomeConnected={isGGHomeConnected}
@@ -676,6 +694,7 @@ const DeviceDetail = ({ route }) => {
676
694
  style={styles.buttonStar}
677
695
  onPress={isFavorite ? removeFromFavorites : addToFavorites}
678
696
  testID={TESTID.HEADER_DEVICE_BUTTON_STAR}
697
+ accessibilityLabel={TESTID.HEADER_DEVICE_BUTTON_STAR}
679
698
  >
680
699
  {isFavorite ? (
681
700
  <IconFill name="star" size={25} color={Colors.Yellow6} />
@@ -725,7 +744,7 @@ const DeviceDetail = ({ route }) => {
725
744
  renderSensorConnected()}
726
745
  </View>
727
746
  {isNetworkConnected &&
728
- isConnected &&
747
+ displayValuesData?.isConnected &&
729
748
  isShowSetupEmergencyContact &&
730
749
  canManageSubUnit && (
731
750
  <BottomButtonView
@@ -17,14 +17,16 @@ export const useFavoriteDevice = (device) => {
17
17
  API.DEVICE.ADD_TO_FAVOURITES(device?.id)
18
18
  );
19
19
  success && setAction(Action.ADD_DEVICES_TO_FAVORITES, [device.id]);
20
- }, [device, setAction]);
20
+ // eslint-disable-next-line react-hooks/exhaustive-deps
21
+ }, [device]);
21
22
 
22
23
  const removeFromFavorites = useCallback(async () => {
23
24
  const { success } = await axiosPost(
24
25
  API.DEVICE.REMOVE_FROM_FAVOURITES(device?.id)
25
26
  );
26
27
  success && setAction(Action.REMOVE_DEVICES_FROM_FAVORITES, [device.id]);
27
- }, [device, setAction]);
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ }, [device]);
28
30
 
29
31
  return {
30
32
  isFavorite,
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useState } from 'react';
2
- import { SafeAreaView, StyleSheet, TextInput, View } from 'react-native';
2
+ import { StyleSheet, TextInput, View } from 'react-native';
3
3
  import { useNavigation } from '@react-navigation/native';
4
4
  import { useTranslations } from '../../hooks/Common/useTranslations';
5
5
  import { Section, ViewButtonBottom } from '../../commons';
@@ -53,7 +53,7 @@ export const EmergencyContactsAddNew = ({ route }) => {
53
53
  }, [goBack, group.id, t, textName, textPhone]);
54
54
 
55
55
  return (
56
- <SafeAreaView style={styles.wrap}>
56
+ <View style={styles.wrap}>
57
57
  <WrapHeaderScrollable title={t('create_contact')}>
58
58
  <Section type={'border'}>
59
59
  <TextInput
@@ -87,7 +87,7 @@ export const EmergencyContactsAddNew = ({ route }) => {
87
87
  onRightClick={onSave}
88
88
  />
89
89
  </View>
90
- </SafeAreaView>
90
+ </View>
91
91
  );
92
92
  };
93
93
 
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
- import { SafeAreaView, StyleSheet, View, Platform } from 'react-native';
2
+ import { StyleSheet, View } from 'react-native';
3
3
  import { IconOutline } from '@ant-design/icons-react-native';
4
4
  import { useNavigation } from '@react-navigation/native';
5
5
  import { useTranslations } from '../../hooks/Common/useTranslations';
@@ -70,9 +70,9 @@ export const EmergencyContactsSelectContacts = ({ route }) => {
70
70
  loadMembers(unitId);
71
71
  }, [loadMembers, unitId]);
72
72
  return (
73
- <SafeAreaView style={styles.container}>
73
+ <View style={styles.container}>
74
74
  <WrapHeaderScrollable title={t('select_contacts')} loading={loading}>
75
- <Section type={'border'} style={styles.section}>
75
+ <Section type={'border'}>
76
76
  {dataContact.map((contact, index) => (
77
77
  <RowUser
78
78
  key={contact.id.toString()}
@@ -108,7 +108,7 @@ export const EmergencyContactsSelectContacts = ({ route }) => {
108
108
  onRightClick={goSave}
109
109
  rightDisabled={false}
110
110
  />
111
- </SafeAreaView>
111
+ </View>
112
112
  );
113
113
  };
114
114
 
@@ -117,9 +117,6 @@ const styles = StyleSheet.create({
117
117
  flex: 1,
118
118
  backgroundColor: Colors.Gray2,
119
119
  },
120
- section: {
121
- marginTop: Platform.OS === 'ios' ? 40 : 0,
122
- },
123
120
  buttonRemove: {
124
121
  height: 40,
125
122
  width: 40,
@@ -57,7 +57,6 @@ describe('test NotificationItem', () => {
57
57
  NOTIFICATION_TYPES.NOTIFY_MEMBER_LEAVE_UNIT,
58
58
  NOTIFICATION_TYPES.NOTIFY_REMOVE_SUB_UNIT,
59
59
  NOTIFICATION_TYPES.NOTIFY_REMOVE_DEVICE,
60
- 'default case',
61
60
  ];
62
61
 
63
62
  for (const content_code of listCase2) {
@@ -176,11 +175,8 @@ describe('test NotificationItem', () => {
176
175
  tree = create(wrapComponent(item));
177
176
  });
178
177
  const instance = tree.root;
179
- const button = instance.findByType(TouchableOpacity);
180
- act(() => {
181
- button.props.onPress();
182
- });
183
- expect(mockNavigate).not.toHaveBeenCalled();
178
+ const button = instance.findAllByType(TouchableOpacity);
179
+ expect(button).toHaveLength(0);
184
180
  });
185
181
 
186
182
  const listSensorType2 = [
@@ -249,25 +245,8 @@ describe('test NotificationItem', () => {
249
245
  tree = create(wrapComponent(item));
250
246
  });
251
247
  const instance = tree.root;
252
- const button = instance.findByType(TouchableOpacity);
253
- act(() => {
254
- button.props.onPress();
255
- });
256
- expect(mockNavigate).not.toHaveBeenCalledWith();
257
- });
258
-
259
- test('test render Notify params is object', () => {
260
- item.content_code = 'NEW CASE';
261
- item.params = { unit_id: 1 };
262
- act(() => {
263
- tree = create(wrapComponent(item));
264
- });
265
- const instance = tree.root;
266
- const button = instance.findByType(TouchableOpacity);
267
- act(() => {
268
- button.props.onPress();
269
- });
270
- expect(mockNavigate).not.toHaveBeenCalledWith();
248
+ const button = instance.findAllByType(TouchableOpacity);
249
+ expect(button).toHaveLength(0);
271
250
  });
272
251
 
273
252
  test('test notify emergency ', () => {
@@ -332,10 +311,7 @@ describe('test NotificationItem', () => {
332
311
  tree = create(wrapComponent(item));
333
312
  });
334
313
  const instance = tree.root;
335
- const button = instance.findByType(TouchableOpacity);
336
- act(() => {
337
- button.props.onPress();
338
- });
339
- expect(mockNavigate).not.toHaveBeenCalled();
314
+ const button = instance.findAllByType(TouchableOpacity);
315
+ expect(button).toHaveLength(0);
340
316
  });
341
317
  });
@@ -195,13 +195,7 @@ const NotificationItem = memo(({ item }) => {
195
195
  iconContent: <Image source={Images.logo} style={styles.logo} />,
196
196
  };
197
197
  default:
198
- return {
199
- content: customColorText(
200
- t('this_notification_will_be_updated_soon')
201
- ),
202
- redirect: () => null,
203
- iconContent: <Image source={Images.logo} style={styles.logo} />,
204
- };
198
+ return null;
205
199
  }
206
200
 
207
201
  case NOTIFICATION_TYPES.NOTIFY_REMOVE_UNIT:
@@ -354,20 +348,10 @@ const NotificationItem = memo(({ item }) => {
354
348
  iconContent: <Image source={Images.logo} style={styles.logo} />,
355
349
  };
356
350
  default:
357
- return {
358
- content: customColorText(
359
- t('this_notification_will_be_updated_soon')
360
- ),
361
- redirect: () => null,
362
- iconContent: <Image source={Images.logo} style={styles.logo} />,
363
- };
351
+ return null;
364
352
  }
365
353
  default:
366
- return {
367
- content: customColorText(t('this_notification_will_be_updated_soon')),
368
- redirect: () => null,
369
- iconContent: <Image source={Images.logo} style={styles.logo} />,
370
- };
354
+ return null;
371
355
  }
372
356
  }, [content_code, customColorText, navigation, params, t]);
373
357
 
@@ -168,7 +168,7 @@ describe('Test ScriptDetail', () => {
168
168
  });
169
169
  const instance = tree.root;
170
170
  const buttonStar = instance.find(
171
- (el) => el.props.testID === TESTID.HEADER_DEVICE_BUTTON_STAR
171
+ (el) => el.props.testID === TESTID.HEADER_SCRIPT_DETAIL_BUTTON_STAR
172
172
  );
173
173
  mock.onPost(API.AUTOMATE.STAR_SCRIPT(1)).reply(200);
174
174
  await act(async () => {
@@ -321,7 +321,8 @@ const ScriptDetail = ({ route }) => {
321
321
  <PreventDoubleTouch
322
322
  style={[styles.buttonStar, styles.headerButton]}
323
323
  onPress={onPressStar}
324
- testID={TESTID.HEADER_DEVICE_BUTTON_STAR}
324
+ testID={TESTID.HEADER_SCRIPT_DETAIL_BUTTON_STAR}
325
+ accessibilityLabel={TESTID.HEADER_SCRIPT_DETAIL_BUTTON_STAR}
325
326
  >
326
327
  {isStarred ? (
327
328
  <IconFill name="star" size={25} color={Colors.Yellow6} />
@@ -27,7 +27,7 @@ const SensorItem = ({
27
27
  isChecked,
28
28
  } = item;
29
29
  const [dataConfig, setDataConfig] = useState([
30
- ...actions,
30
+ ...actions.map((i) => ({ ...i, isControl: true })),
31
31
  ...read_configs.map((i) => ({ ...i, isConfig: true })),
32
32
  ]);
33
33
 
@@ -84,6 +84,9 @@ const SensorItem = ({
84
84
  titleStyle={styles.titleStyle}
85
85
  key={i.id}
86
86
  idGroup={idGroup}
87
+ isConfig={i.isConfig}
88
+ isControl={i.isControl}
89
+ wrapStyle={styles.wrapStyleTitle}
87
90
  />
88
91
  ));
89
92
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -49,6 +49,7 @@ export default StyleSheet.create({
49
49
  titleStyle: {
50
50
  fontSize: 16,
51
51
  fontWeight: 'normal',
52
+ width: 200,
52
53
  },
53
54
  wrapExpand: {
54
55
  marginTop: 10,
@@ -57,4 +58,7 @@ export default StyleSheet.create({
57
58
  width: normalize(20),
58
59
  height: normalize(20),
59
60
  },
61
+ wrapStyleTitle: {
62
+ justifyContent: 'space-between',
63
+ },
60
64
  });
@@ -14,4 +14,8 @@ export default StyleSheet.create({
14
14
  fontWeight: '600',
15
15
  fontStyle: 'normal',
16
16
  },
17
+ wrapRow: {
18
+ flexDirection: 'row',
19
+ alignItems: 'center',
20
+ },
17
21
  });
@@ -1,6 +1,7 @@
1
1
  import React, { memo, useEffect, useState } from 'react';
2
2
  import { View, Text } from 'react-native';
3
3
  import { CheckBoxCustom } from '.';
4
+ import t from '../../../hooks/Common/useTranslations';
4
5
  import styles from './Styles/TitleCheckBoxStyles';
5
6
 
6
7
  const TitleCheckBox = ({
@@ -12,6 +13,8 @@ const TitleCheckBox = ({
12
13
  wrapStyle,
13
14
  titleStyle,
14
15
  idGroup,
16
+ isConfig,
17
+ isControl,
15
18
  }) => {
16
19
  const [checked, setChecked] = useState(isChecked);
17
20
  const handleOnPress = () => {
@@ -26,14 +29,20 @@ const TitleCheckBox = ({
26
29
 
27
30
  return (
28
31
  <View style={[styles.wrap, wrapStyle]}>
29
- <CheckBoxCustom
30
- isChecked={checked}
31
- onPress={handleOnPress}
32
- wrapStyle={wrapCheckBoxStyle}
33
- />
34
- <Text onPress={handleOnPress} style={[styles.title, titleStyle]}>
35
- {title}
36
- </Text>
32
+ <View style={styles.wrapRow}>
33
+ <CheckBoxCustom
34
+ isChecked={checked}
35
+ onPress={handleOnPress}
36
+ wrapStyle={wrapCheckBoxStyle}
37
+ />
38
+ <Text onPress={handleOnPress} style={[styles.title, titleStyle]}>
39
+ {title}
40
+ </Text>
41
+ </View>
42
+ <View>
43
+ {isControl && <Text>{t('can_control')}</Text>}
44
+ {isConfig && <Text>{t('view_only')}</Text>}
45
+ </View>
37
46
  </View>
38
47
  );
39
48
  };
@@ -108,10 +108,12 @@ const UnitDetail = ({ route }) => {
108
108
  rawUnitData.stations.unshift({
109
109
  isOneTap: true,
110
110
  name: t('smart'),
111
+ id: 'smart',
111
112
  });
112
113
  rawUnitData.stations.unshift({
113
114
  isFavorites: true,
114
115
  name: t('favorites'),
116
+ id: 'favorites',
115
117
  });
116
118
  },
117
119
  [t]
@@ -302,7 +304,8 @@ const UnitDetail = ({ route }) => {
302
304
  clearTimeout(to);
303
305
  }, 3000);
304
306
  }
305
- }, [isFirstOpenCamera, isIOS, setAction]);
307
+ // eslint-disable-next-line react-hooks/exhaustive-deps
308
+ }, [isFirstOpenCamera, isIOS]);
306
309
 
307
310
  return (
308
311
  <WrapParallaxScrollView
@@ -331,6 +334,8 @@ const UnitDetail = ({ route }) => {
331
334
  onSnapToItem={onSnapToItem}
332
335
  indexStation={indexStation}
333
336
  idLabelScrollView={TESTID.NAV_LIST}
337
+ idLabelItem={TESTID.SUB_UNIT_NAME}
338
+ idLabelIconBars={TESTID.NAVBAR_ICON_BARS}
334
339
  />
335
340
  {renderDetailSubUnit()}
336
341
  {!!unit.can_add && unit.stations.length === 0 && (
@@ -21,6 +21,7 @@ import { SCContext } from '../../context';
21
21
  import { Action } from '../../context/actionType';
22
22
  import { axiosGet, axiosPost } from '../../utils/Apis/axios';
23
23
  import { API, Colors } from '../../configs';
24
+ import { TESTID } from '../../configs/Constants';
24
25
  import styles from './SelectAddToFavoritesStyles';
25
26
 
26
27
  const SelectAddToFavorites = memo(({ route }) => {
@@ -136,7 +137,12 @@ const SelectAddToFavorites = memo(({ route }) => {
136
137
 
137
138
  const rightComponent = useMemo(
138
139
  () => (
139
- <TouchableOpacity style={styles.buttonClose} onPress={goBack}>
140
+ <TouchableOpacity
141
+ testID={TESTID.ICON_BACK}
142
+ accessibilityLabel={TESTID.ICON_BACK}
143
+ style={styles.buttonClose}
144
+ onPress={goBack}
145
+ >
140
146
  <Icon name={'close'} size={24} color={Colors.Black} />
141
147
  </TouchableOpacity>
142
148
  ),
@@ -151,6 +157,8 @@ const SelectAddToFavorites = memo(({ route }) => {
151
157
  style={styles.wrap}
152
158
  contentContainerStyle={styles.contentContainerStyle}
153
159
  scrollIndicatorInsets={{ right: 1 }}
160
+ testID={TESTID.LIST_FAVORITES}
161
+ idLabelScrollView={TESTID.LIST_FAVORITES}
154
162
  >
155
163
  <Text bold type="H2" style={styles.title}>
156
164
  {t('select_device')}
@@ -162,6 +170,8 @@ const SelectAddToFavorites = memo(({ route }) => {
162
170
  listMenuItem={listMenuItem}
163
171
  onSnapToItem={onSnapToItem}
164
172
  indexStation={indexStation}
173
+ idLabelItem={TESTID.SUB_UNIT_FAVORITES}
174
+ idLabelIconBars={TESTID.NAVBAR_ICON_BARS_ADD_FAVORITES}
165
175
  style={styles.navbar}
166
176
  />
167
177
  )}
@@ -9,7 +9,7 @@ import ValueChange from '../../../../../assets/images/ValueChange.svg';
9
9
  import Schedule from '../../../../../assets/images/Schedule.svg';
10
10
  import Event from '../../../../../assets/images/Event.svg';
11
11
  import styles from './styles';
12
- import { AUTOMATE_TYPE } from '../../../../configs/Constants';
12
+ import { AUTOMATE_TYPE, TESTID } from '../../../../configs/Constants';
13
13
  import { Colors } from '../../../../configs';
14
14
 
15
15
  const AutomateScript = ({ automate, onPress, isSelected }) => {
@@ -36,7 +36,10 @@ const AutomateScript = ({ automate, onPress, isSelected }) => {
36
36
  };
37
37
 
38
38
  return (
39
- <TouchableWithoutFeedback onPress={_onPress}>
39
+ <TouchableWithoutFeedback
40
+ onPress={_onPress}
41
+ accessibilityLabel={`${TESTID.TOUCHABLE_ACTION_ADD_ITEM_AUTOMATE_FAVORITE}-${automate.id}`}
42
+ >
40
43
  <View style={[styles.container, isSelected && styles.active]}>
41
44
  <View style={styles.boxIcon}>{displayIcon()}</View>
42
45
  <View>
@@ -687,6 +687,7 @@
687
687
  "text_notification_content_update_address": "New address of **unit_name** has been updated by **unit_owner_name** to **unit_address**.",
688
688
  "text_notification_content_emergency": "The Emergency button has been activated at **unit_name**: **sub_unit_name**. Please check now!",
689
689
  "text_notification_content_emergency_resolve": "Emergency situation activated at **unit_name**: **sub_unit_name** has resolved.",
690
+ "text_notification_content_invite_member": "**unit_owner_name** invited you to join **unit_name**.",
690
691
  "this_spot_does_not_exsit": "This spot does not exist",
691
692
  "please_scan_again_or_contact_the_parking_manager": "Please scan again or contact the parking manager",
692
693
  "this_spot_does_not_support_to_scan": "This spot does not support to scan",
@@ -979,10 +980,14 @@
979
980
  "activated": "Activated",
980
981
  "text_unit_add_to_favorites_no_devices": "You don't have any devices or all your devices was added to your favorites",
981
982
  "not_found": "Not found",
983
+ "view_only": "View only",
984
+ "can_control": "Can control",
982
985
  "not_activated": "Not activated",
983
986
  "open": "Open",
984
987
  "close": "Close",
985
988
  "create_contact_success": "Create contact success!",
989
+ "can_not_login_to_current_ssid":"Can't login to current SSID",
990
+ "confirm_password_not_match": "Confirm password does not match",
986
991
  "templates": "Templates",
987
992
  "template": "Template",
988
993
  "gateways": "Gateways",
@@ -702,6 +702,7 @@
702
702
  "text_notification_content_update_address": "Địa chỉ mới của **unit_name** vừa được cập nhật bởi **unit_owner_name** thành **unit_address**.",
703
703
  "text_notification_content_emergency": "Một tình huống khẩn cấp vừa được kích hoạt tại **unit_name**: **sub_unit_name**. Vui lòng kiểm tra ngay!",
704
704
  "text_notification_content_emergency_resolve": "Tình huống khẩn cấp được kích hoạt tại **unit_name**: **sub_unit_name** đã được giải quyết.",
705
+ "text_notification_content_invite_member": "**unit_owner_name** mời bạn đến địa điểm **unit_name**.",
705
706
  "this_spot_does_not_exsit": "Vị trí đỗ này không tồn tại",
706
707
  "please_scan_again_or_contact_the_parking_manager": "Vui lòng quét lại hoặc liên hệ với người quản lý bãi đậu xe",
707
708
  "this_spot_does_not_support_to_scan": "Vị trí đỗ này không hỗ trợ quét",
@@ -978,10 +979,14 @@
978
979
  "activated": "Được kích hoạt",
979
980
  "text_unit_add_to_favorites_no_devices": "Bạn không có thiết bị nào hoặc tất cả thiết bị của bạn đã được thêm vào yêu thích",
980
981
  "not_found": "Không tìm thấy",
982
+ "view_only": "Chỉ xem",
983
+ "can_control": "Có thể điều khiển",
981
984
  "not_activated": "Không kích hoạt",
982
985
  "open": "Mở",
983
986
  "close": "Đóng",
984
987
  "create_contact_success": "Tạo liên hệ thành công!",
988
+ "can_not_login_to_current_ssid":"Không thể đăng nhập vào SSID hiện tại",
989
+ "confirm_password_not_match": "Mật khẩu xác nhận không trùng khớp",
985
990
  "templates": "Mẫu",
986
991
  "template": "Mẫu",
987
992
  "gateways": "Cổng vào",