@eohjsc/react-native-smart-city 0.3.13 → 0.3.16

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.13",
4
+ "version": "0.3.16",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -6,6 +6,8 @@ import { useConfigGlobalState } from '../../../iot/states';
6
6
  import OnOffButtonTemplate from './OnOffButtonTemplate';
7
7
  import OnOffSimpleTemplate from './OnOffSimpleTemplate';
8
8
 
9
+ let temp;
10
+
9
11
  const getComponent = (template) => {
10
12
  switch (template) {
11
13
  case 'on_off_button_action_template': // todo refactor later with backend
@@ -20,11 +22,28 @@ const getComponent = (template) => {
20
22
  const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
21
23
  const { configuration } = actionGroup;
22
24
  const { action_data, action_on_data, action_off_data } = configuration;
23
- const [isOn, setIsOn] = useState(null);
24
25
 
25
26
  // eslint-disable-next-line no-unused-vars
26
27
  const [configValues, _] = useConfigGlobalState('configValues');
27
28
 
29
+ const getIsOnValue = useCallback(() => {
30
+ const { is_on_value, config } = configuration;
31
+ const configValue = configValues[config];
32
+ if (is_on_value && is_on_value.length > 0) {
33
+ return is_on_value.includes(configValue);
34
+ }
35
+ return !!configValue;
36
+ }, [configValues, configuration]);
37
+
38
+ const [isOn, setIsOn] = useState(false);
39
+ const [tempIsOn, setTempIsOn] = useState(getIsOnValue());
40
+
41
+ const updateStatusFromPusher = () => {
42
+ setTimeout(() => {
43
+ setTempIsOn(temp);
44
+ }, 2500);
45
+ };
46
+
28
47
  const actionName = useCallback(
29
48
  (text) => {
30
49
  const actionNameType = `${
@@ -34,7 +53,9 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
34
53
  },
35
54
  [actionGroup?.title, sensor?.name]
36
55
  );
56
+
37
57
  const triggerAction = useCallback(async () => {
58
+ setTempIsOn((prev) => !prev);
38
59
  switch (sensor?.device_type) {
39
60
  case DEVICE_TYPE.ZIGBEE:
40
61
  if (action_on_data && action_off_data) {
@@ -53,6 +74,13 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
53
74
  }
54
75
  }
55
76
  break;
77
+ case DEVICE_TYPE.LG_THINQ:
78
+ if (action_data) {
79
+ await doAction(action_data, JSON.stringify({ value: !isOn }));
80
+ }
81
+
82
+ updateStatusFromPusher();
83
+ break;
56
84
  default:
57
85
  if (action_data) {
58
86
  if (isOn) {
@@ -88,19 +116,12 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
88
116
  ]);
89
117
 
90
118
  useEffect(() => {
91
- const { is_on_value, config } = configuration;
92
- const configValue = configValues[config];
93
- if (is_on_value && is_on_value.length > 0) {
94
- setIsOn(is_on_value.includes(configValue));
95
- return;
96
- }
97
- setIsOn(configValue);
98
- }, [
99
- configuration.config,
100
- configValues,
101
- configuration.is_on_value,
102
- configuration,
103
- ]);
119
+ setIsOn(getIsOnValue());
120
+ }, [getIsOnValue]);
121
+
122
+ useEffect(() => {
123
+ temp = isOn;
124
+ }, [isOn]);
104
125
 
105
126
  useEffect(() => {
106
127
  if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
@@ -118,7 +139,7 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
118
139
  return (
119
140
  <>
120
141
  <Component
121
- isOn={isOn}
142
+ isOn={tempIsOn}
122
143
  triggerAction={triggerAction}
123
144
  actionGroup={actionGroup}
124
145
  disabled={!action_data && !action_on_data && !action_off_data}
@@ -90,7 +90,7 @@ const TimerActionTemplate = ({ actionGroup = {}, doAction, sensor = {} }) => {
90
90
 
91
91
  const doActionTime = useCallback(
92
92
  (hour, minute) => {
93
- doAction(configuration.action_data, [hour, minute]);
93
+ doAction(configuration.action_data, JSON.stringify({ hour, minute }));
94
94
  if (sensor.is_managed_by_backend) {
95
95
  hour !== undefined &&
96
96
  minute !== undefined &&
@@ -113,7 +113,7 @@ const TimerActionTemplate = ({ actionGroup = {}, doAction, sensor = {} }) => {
113
113
 
114
114
  const doActionHour = useCallback(
115
115
  (hour) => {
116
- doAction(configuration.action_data, hour);
116
+ doAction(configuration.action_data, JSON.stringify({ hour }));
117
117
  if (
118
118
  sensor.is_managed_by_backend &&
119
119
  sensor.device_type !== 'GOOGLE_HOME'
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable promise/prefer-await-to-callbacks */
2
- import { TESTID } from '../../../configs/Constants';
2
+ import { DEVICE_TYPE, TESTID } from '../../../configs/Constants';
3
3
  import { watchMultiConfigs } from '../../../iot/Monitor';
4
4
  import React from 'react';
5
5
  import { View } from 'react-native';
@@ -149,6 +149,7 @@ describe('Test OnOffTemplate', () => {
149
149
  await act(async () => {
150
150
  tree = await create(wrapComponent(actionGroup, mockDoAction, sensor));
151
151
  });
152
+
152
153
  const instance = tree.root;
153
154
  const template = instance.findByType(OnOffSimpleTemplate);
154
155
  expect(template.props.isOn).toEqual(true);
@@ -230,6 +231,39 @@ describe('Test OnOffTemplate', () => {
230
231
  expect(mockDoAction).toHaveBeenCalledWith(action_data, false);
231
232
  });
232
233
 
234
+ test('render with template OnOffSimpleActionTemplate with just action_data lg_thinq', async () => {
235
+ actionGroup = {
236
+ template: 'OnOffSimpleActionTemplate',
237
+ configuration: {
238
+ config: 5,
239
+ action_data: action_data,
240
+ icon: 'up',
241
+ is_on_value: [2],
242
+ },
243
+ title: 'Turn on / off',
244
+ };
245
+ const mockDoAction = jest.fn();
246
+ await act(async () => {
247
+ tree = await create(
248
+ wrapComponent(actionGroup, mockDoAction, {
249
+ device_type: DEVICE_TYPE.LG_THINQ,
250
+ })
251
+ );
252
+ });
253
+ const instance = tree.root;
254
+ const template = instance.findAllByType(OnOffSimpleTemplate);
255
+ expect(template).toHaveLength(1);
256
+ expect(template[0].props.disabled).toBeFalsy();
257
+
258
+ await act(async () => {
259
+ await template[0].props.triggerAction();
260
+ });
261
+ expect(mockDoAction).toHaveBeenCalledWith(
262
+ action_data,
263
+ JSON.stringify({ value: false })
264
+ );
265
+ });
266
+
233
267
  test('render with template OnOffSimpleActionTemplate disabled', async () => {
234
268
  actionGroup = {
235
269
  template: 'OnOffSimpleActionTemplate',
@@ -89,6 +89,9 @@ describe('Test TimerActionTemplate without config value', () => {
89
89
  await dateTimePicker.props.onConfirm();
90
90
  });
91
91
  expect(dateTimePicker.props.isVisible).toBeFalsy();
92
- expect(mockDoAction).toHaveBeenCalledWith(action_data, [11, 0]);
92
+ expect(mockDoAction).toHaveBeenCalledWith(
93
+ action_data,
94
+ JSON.stringify({ hour: 11, minute: 0 })
95
+ );
93
96
  });
94
97
  });
@@ -24,7 +24,7 @@ const SubUnitFavorites = ({
24
24
  const { getStatus, serverDown } = useSensorsStatus(unit, favoriteDevices);
25
25
 
26
26
  const handleOnAddNew = () => {
27
- navigate(Routes.SelectDevices, {
27
+ navigate(Routes.SelectFavoritesDevices, {
28
28
  unitId: unit.id,
29
29
  });
30
30
  };
@@ -113,6 +113,7 @@ describe('Test useRemoteControl', () => {
113
113
  'bluetooth',
114
114
  actionName
115
115
  );
116
+ expect(sendCommandOverInternet).toBeCalledTimes(1);
116
117
  expect(sendCommandOverGoogleHome).not.toBeCalled();
117
118
  expect(sendCommandOverLGThinq).not.toBeCalled();
118
119
  });
@@ -190,7 +191,6 @@ describe('Test useRemoteControl', () => {
190
191
  act(() => {
191
192
  sendRemoteCommand.current(sensor, action, data, userId, actionName);
192
193
  });
193
- expect(sendCommandOverLGThinq).toBeCalledWith(sensor, action, data);
194
194
  expect(sendCommandOverBluetooth).not.toBeCalled();
195
195
  expect(sendCommandOverGoogleHome).not.toBeCalled();
196
196
  expect(sendCommandOverInternet).not.toBeCalled();
@@ -2,7 +2,6 @@ import { useCallback } from 'react';
2
2
  import { useSCContextSelector } from '../../context';
3
3
  import { sendCommandOverGoogleHome } from '../../iot/RemoteControl/GoogleHome';
4
4
  import { sendCommandOverInternet } from '../../iot/RemoteControl/Internet';
5
- import { sendCommandOverLGThinq } from '../../iot/RemoteControl/LG';
6
5
  import {
7
6
  sendCommandOverBluetooth,
8
7
  SEND_COMMAND_OVER_BLUETOOTH_FAIL,
@@ -19,6 +18,7 @@ const useRemoteControl = () => {
19
18
  async (sensor, action, data, userId, actionName) => {
20
19
  // No action, raise not authorized
21
20
  let result = false;
21
+
22
22
  if (!action) {
23
23
  ToastBottomHelper.error(
24
24
  t('your_account_has_not_been_authorized_to_control')
@@ -30,6 +30,7 @@ const useRemoteControl = () => {
30
30
  try {
31
31
  result = await sendCommandOverBluetooth(sensor, action, data, userId);
32
32
  } catch (err) {
33
+ result = false;
33
34
  if (err === SEND_COMMAND_OVER_BLUETOOTH_FAIL) {
34
35
  result = await sendCommandOverInternet(
35
36
  sensor,
@@ -45,8 +46,9 @@ const useRemoteControl = () => {
45
46
  }
46
47
 
47
48
  if (
48
- action.command_prefer_over_internet ||
49
- action.command_prefer_over_bluetooth
49
+ (action.command_prefer_over_internet ||
50
+ action.command_prefer_over_bluetooth) &&
51
+ !result
50
52
  ) {
51
53
  result = await sendCommandOverInternet(
52
54
  sensor,
@@ -56,7 +58,7 @@ const useRemoteControl = () => {
56
58
  );
57
59
  }
58
60
 
59
- if (action.command_prefer_over_googlehome) {
61
+ if (action.command_prefer_over_googlehome && !result) {
60
62
  result = await sendCommandOverGoogleHome(
61
63
  ggHomeConnections,
62
64
  sensor,
@@ -64,10 +66,6 @@ const useRemoteControl = () => {
64
66
  data
65
67
  );
66
68
  }
67
-
68
- if (action.command_prefer_over_lg) {
69
- result = await sendCommandOverLGThinq(sensor, action, data);
70
- }
71
69
  return result;
72
70
  },
73
71
  [ggHomeConnections]
@@ -94,17 +94,17 @@ export async function updateStateByLgThinq(device_id, data) {
94
94
  setConfigGlobalState('configValues', { ...configValues });
95
95
  }
96
96
 
97
- export async function fetchDeviceStatusLG(sensor) {
97
+ export async function fetchDeviceStatusLG(endDevice) {
98
98
  // still need some delay
99
99
  setTimeout(async () => {
100
100
  const { success, data: dataStatus } = await axiosGet(
101
- API.IOT.LG.DEVICE_STATUS(sensor.id),
101
+ API.IOT.LG.DEVICE_STATUS(endDevice.id),
102
102
  {},
103
103
  true
104
104
  );
105
105
 
106
106
  if (success) {
107
- updateStateByLgThinq(sensor.lg_device_id, dataStatus);
107
+ updateStateByLgThinq(endDevice.lg_device_id, dataStatus);
108
108
  }
109
109
  }, 1000);
110
110
  }
@@ -128,11 +128,11 @@ export const lgThinqConnect = async (options) => {
128
128
  const option = options[i];
129
129
  for (let j = 0; j < option.lg_devices.length; j++) {
130
130
  const lgDevice = option.lg_devices[j];
131
- const sensor = {
132
- id: lgDevice.sensor_id,
131
+ const endDevice = {
132
+ id: lgDevice.end_device_id,
133
133
  lg_device_id: lgDevice.device_id,
134
134
  };
135
- await fetchDeviceStatusLG(sensor);
135
+ await fetchDeviceStatusLG(endDevice);
136
136
  }
137
137
  }
138
138
  };
@@ -107,7 +107,7 @@ describe('Remote Control LG Thinq', () => {
107
107
  lg_devices: [
108
108
  {
109
109
  id: 1,
110
- sensor_id: 2,
110
+ end_device_id: 2,
111
111
  device_id: 'DEVICE_ID',
112
112
  configs: [
113
113
  {
@@ -188,7 +188,7 @@ describe('Remote Control LG Thinq', () => {
188
188
  lg_devices: [
189
189
  {
190
190
  id: 1,
191
- sensor_id: 2,
191
+ end_device_id: 2,
192
192
  device_id: 'DEVICE_ID',
193
193
  configs: [
194
194
  {
@@ -51,7 +51,7 @@ import EmergencySetting from '../screens/EmergencySetting';
51
51
  import ConfirmUnitDeletion from '../screens/ConfirmUnitDeletion';
52
52
  import InfoMemberUnit from '../screens/Sharing/InfoMemberUnit';
53
53
  import EnterPassword from '../screens/EnterPassword';
54
- import SelectDevices from '../screens/Unit/SelectDevices';
54
+ import SelectFavoritesDevices from '../screens/Unit/SelectFavoritesDevices';
55
55
  import { HanetCameraStack } from './HanetCameraStack';
56
56
  import { axiosGet } from '../utils/Apis/axios';
57
57
  import { API } from '../configs';
@@ -402,8 +402,8 @@ export const UnitStack = memo((props) => {
402
402
  }}
403
403
  />
404
404
  <Stack.Screen
405
- name={Route.SelectDevices}
406
- component={SelectDevices}
405
+ name={Route.SelectFavoritesDevices}
406
+ component={SelectFavoritesDevices}
407
407
  options={{
408
408
  headerShown: false,
409
409
  }}
@@ -41,6 +41,7 @@ const SelectAction = memo(({ route }) => {
41
41
  isAutomateTab,
42
42
  isCreateNewAction,
43
43
  isMultiUnits,
44
+ oldType,
44
45
  } = route.params;
45
46
  const [data, setData] = useState([]);
46
47
  const [actions, setActions] = useState([]);
@@ -257,7 +258,7 @@ const SelectAction = memo(({ route }) => {
257
258
  navigate(Routes.ScriptDetail, {
258
259
  id: automateId,
259
260
  name: scriptName,
260
- type: type,
261
+ type: oldType,
261
262
  havePermission: true,
262
263
  unit,
263
264
  isMultiUnits,
@@ -116,6 +116,7 @@ const SelectSensorDevices = memo(({ route }) => {
116
116
  isAutomateTab,
117
117
  isCreateNewAction,
118
118
  isMultiUnits,
119
+ oldType,
119
120
  });
120
121
  }, [
121
122
  navigate,
@@ -131,6 +132,7 @@ const SelectSensorDevices = memo(({ route }) => {
131
132
  isAutomateTab,
132
133
  isCreateNewAction,
133
134
  isMultiUnits,
135
+ oldType,
134
136
  ]);
135
137
 
136
138
  const onPressClose = useCallback(() => {
@@ -49,6 +49,7 @@ export const SensorDisplayItem = ({
49
49
  },
50
50
  [sensor, userId, sendRemoteCommand]
51
51
  );
52
+
52
53
  if (item.configuration.type === 'compass') {
53
54
  setShowWindDirection(true);
54
55
  }
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
  import { create, act } from 'react-test-renderer';
3
+ import Toast from 'react-native-toast-message';
4
+
3
5
  import MockAdapter from 'axios-mock-adapter';
4
6
  import { SCProvider } from '../../../context';
5
7
  import { mockSCStore } from '../../../context/mockStore';
@@ -117,6 +119,30 @@ describe('Test ScriptDetail', () => {
117
119
  expect(alertAction.props.visible).toBeFalsy();
118
120
  });
119
121
 
122
+ test('test rename script failed', async () => {
123
+ await act(async () => {
124
+ tree = await create(wrapComponent(route));
125
+ });
126
+ const instance = tree.root;
127
+ const menu = instance.findByType(MenuActionMore);
128
+ const alertAction = instance.findByType(AlertAction);
129
+ const rename = menu.props.listMenuItem[0];
130
+
131
+ await act(async () => {
132
+ await menu.props.onItemClick(rename);
133
+ await menu.props.hideComplete();
134
+ });
135
+ expect(menu.props.isVisible).toBeFalsy();
136
+ expect(alertAction.props.visible).toBeTruthy();
137
+
138
+ mock.onPatch(API.AUTOMATE.SCRIPT(1)).reply(500, { name: 'new_name' });
139
+ await act(async () => {
140
+ await alertAction.props.rightButtonClick();
141
+ });
142
+ expect(alertAction.props.visible).toBeFalsy();
143
+ expect(Toast.show).toHaveBeenCalled();
144
+ });
145
+
120
146
  test('test delete script', async () => {
121
147
  await act(async () => {
122
148
  tree = await create(wrapComponent(route));
@@ -99,9 +99,14 @@ const ScriptDetail = ({ route }) => {
99
99
  name: inputName,
100
100
  }
101
101
  );
102
- success && setScriptName(script.name);
102
+ if (success) {
103
+ setScriptName(script.name);
104
+ ToastBottomHelper.success(t('rename_successfully'));
105
+ } else {
106
+ ToastBottomHelper.error(t('rename_failed'));
107
+ }
103
108
  hideAlertAction();
104
- }, [id, inputName, hideAlertAction]);
109
+ }, [id, inputName, hideAlertAction, t]);
105
110
 
106
111
  const deleteScript = useCallback(async () => {
107
112
  hideAlertAction();
@@ -113,12 +118,16 @@ const ScriptDetail = ({ route }) => {
113
118
  } else {
114
119
  goBack();
115
120
  }
121
+ ToastBottomHelper.success(t('removed_successfully'));
122
+ } else {
123
+ ToastBottomHelper.error(t('remove_failed'));
116
124
  }
117
125
  }, [
118
126
  hideAlertAction,
119
127
  id,
120
128
  isCreateScriptSuccess,
121
129
  isCreateNewAction,
130
+ t,
122
131
  dispatch,
123
132
  isAutomateTab,
124
133
  goBack,
@@ -257,7 +257,6 @@ const UnitDetail = ({ route }) => {
257
257
  unit={unit}
258
258
  favoriteDevices={favoriteDevices}
259
259
  favoriteAutomates={favoriteAutomates}
260
- wrapItemStyle={styles.wrapItemStyle}
261
260
  />
262
261
  );
263
262
  }
@@ -20,9 +20,9 @@ import { SCContext } from '../../context';
20
20
  import { Action } from '../../context/actionType';
21
21
  import { axiosGet, axiosPost } from '../../utils/Apis/axios';
22
22
  import { API, Colors } from '../../configs';
23
- import styles from './SelectDevicesStyles';
23
+ import styles from './SelectFavoritesDevicesStyles';
24
24
 
25
- const SelectDevices = memo(({ route }) => {
25
+ const SelectFavoritesDevices = memo(({ route }) => {
26
26
  const t = useTranslations();
27
27
  const { goBack } = useNavigation();
28
28
  const { unitId } = route.params;
@@ -32,7 +32,7 @@ const SelectDevices = memo(({ route }) => {
32
32
  const [indexStation, setIndexStation] = useState(0);
33
33
  const [stations, setStations] = useState([]);
34
34
  const [selectedIds, setSelectedIds] = useState([]);
35
- const [loading, setLoading] = useState(false);
35
+ const [loading, setLoading] = useState(true);
36
36
 
37
37
  const fetchData = useCallback(async () => {
38
38
  setLoading(true);
@@ -116,7 +116,7 @@ const SelectDevices = memo(({ route }) => {
116
116
  {t('select_device')}
117
117
  </Text>
118
118
 
119
- {listStation.length ? (
119
+ {!!listStation.length && (
120
120
  <NavBar
121
121
  listStation={listStation}
122
122
  listMenuItem={listMenuItem}
@@ -124,7 +124,8 @@ const SelectDevices = memo(({ route }) => {
124
124
  indexStation={indexStation}
125
125
  style={styles.navbar}
126
126
  />
127
- ) : (
127
+ )}
128
+ {!listStation.length && !loading && (
128
129
  <View style={styles.noneData}>
129
130
  <Text center>{t('text_unit_add_to_favorites_no_devices')}</Text>
130
131
  </View>
@@ -155,4 +156,4 @@ const SelectDevices = memo(({ route }) => {
155
156
  );
156
157
  });
157
158
 
158
- export default SelectDevices;
159
+ export default SelectFavoritesDevices;
@@ -4,7 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
4
4
 
5
5
  import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
- import SelectDevices from '../SelectDevices';
7
+ import SelectFavoritesDevices from '../SelectFavoritesDevices';
8
8
  import Device from '../../AddNewAction/Device';
9
9
  import BottomButtonView from '../../../commons/BottomButtonView';
10
10
  import { API } from '../../../configs';
@@ -12,7 +12,7 @@ import api from '../../../utils/Apis/axios';
12
12
 
13
13
  const wrapComponent = (route) => (
14
14
  <SCProvider initState={mockSCStore({})}>
15
- <SelectDevices route={route} />
15
+ <SelectFavoritesDevices route={route} />
16
16
  </SCProvider>
17
17
  );
18
18
 
@@ -35,7 +35,7 @@ jest.mock('react', () => {
35
35
  };
36
36
  });
37
37
 
38
- describe('Test SelectDevices', () => {
38
+ describe('Test SelectFavoritesDevices', () => {
39
39
  let tree, route;
40
40
 
41
41
  beforeAll(() => {
@@ -1,10 +1,5 @@
1
1
  import { StyleSheet } from 'react-native';
2
- import { Colors, Constants } from '../../configs';
3
-
4
- const marginItem = 12;
5
- const marginHorizontal = 16;
6
- const widthItem = (Constants.width - marginHorizontal * 2 - marginItem) / 2;
7
- const heightItem = (widthItem / 166) * 126;
2
+ import { Colors } from '../../configs';
8
3
 
9
4
  export default StyleSheet.create({
10
5
  container: {
@@ -76,10 +71,6 @@ export default StyleSheet.create({
76
71
  margin: 0,
77
72
  padding: 0,
78
73
  },
79
- wrapItemStyle: {
80
- height: heightItem,
81
- marginVertical: 0,
82
- },
83
74
  camera: {
84
75
  width: 1,
85
76
  height: 1,
@@ -146,7 +146,7 @@ const Routes = {
146
146
  ItemPasscode: 'ItemPasscode',
147
147
  UnitMemberInformation: 'UnitMemberInformation',
148
148
  EnterPassword: 'EnterPassword',
149
- SelectDevices: 'SelectDevices',
149
+ SelectFavoritesDevices: 'SelectFavoritesDevices',
150
150
  };
151
151
 
152
152
  export default Routes;