@eohjsc/react-native-smart-city 0.3.59 → 0.3.61

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.59",
4
+ "version": "0.3.61",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -14,6 +14,7 @@ const ItemQuickAction = memo(({ sensor, wrapperStyle, setStatus }) => {
14
14
  const [isOn, setIsOn] = useState(false);
15
15
 
16
16
  const sendRemoteCommand = useRemoteControl();
17
+ const [processing, setProcessing] = useState(false);
17
18
 
18
19
  useEffect(() => {
19
20
  if (!(sensor.quick_action?.config_id in configValues)) {
@@ -38,7 +39,11 @@ const ItemQuickAction = memo(({ sensor, wrapperStyle, setStatus }) => {
38
39
  }, [isOn, setStatus, sensor.quick_action]);
39
40
 
40
41
  const userId = useSCContextSelector((state) => state?.auth.account.user.id);
41
- const onActionPress = useCallback(() => {
42
+ const onActionPress = useCallback(async () => {
43
+ if (processing) {
44
+ return;
45
+ }
46
+ setProcessing(true);
42
47
  let data = null;
43
48
  if (
44
49
  action?.allow_config_store_value_id === sensor?.quick_action?.config_id
@@ -56,7 +61,7 @@ const ItemQuickAction = memo(({ sensor, wrapperStyle, setStatus }) => {
56
61
  }
57
62
  data = JSON.stringify(data);
58
63
  }
59
- sendRemoteCommand(sensor, action, data, userId);
64
+ await sendRemoteCommand(sensor, action, data, userId);
60
65
  setIsSendingCommand(true);
61
66
 
62
67
  if (!sensor.quick_action.will_auto_update_status) {
@@ -64,7 +69,8 @@ const ItemQuickAction = memo(({ sensor, wrapperStyle, setStatus }) => {
64
69
  setIsOn(action.id === sensor.quick_action.on_action.id);
65
70
  }, sensor.quick_action.interval);
66
71
  }
67
- }, [sensor, action, userId, sendRemoteCommand]);
72
+ setProcessing(false);
73
+ }, [processing, action, sensor, sendRemoteCommand, userId]);
68
74
 
69
75
  if (!action) {
70
76
  return <View />;
@@ -209,11 +209,10 @@ const API = {
209
209
  // NOTE: DEV MODE
210
210
  DEV_MODE: {
211
211
  DASHBOARD: {
212
- DETAIL: (id) =>
213
- `/property_manager/iot_dashboard/dev_mode/templates/${id}/`,
214
- GET_TEMPLATES: '/property_manager/iot_dashboard/dev_mode/templates/',
212
+ DETAIL: (id) => `/property_manager/iot_dashboard/dev_mode/units/${id}/`,
213
+ GET_TEMPLATES: '/property_manager/iot_dashboard/dev_mode/units/',
215
214
  GET_WIDGETS: (templateId) =>
216
- `/property_manager/iot_dashboard/dev_mode/templates/${templateId}/widgets/`,
215
+ `/property_manager/iot_dashboard/dev_mode/units/${templateId}/widgets/`,
217
216
  },
218
217
  GATEWAY: {
219
218
  LIST: () => '/chip_manager/developer_mode_chips/',
@@ -9,6 +9,7 @@ import { sendCommandOverHomeAssistant } from '../../../iot/RemoteControl/HomeAss
9
9
  import { sendCommandOverInternet } from '../../../iot/RemoteControl/Internet';
10
10
  import { SCProvider } from '../../../context';
11
11
  import { mockSCStore } from '../../../context/mockStore';
12
+ import { ToastBottomHelper } from '../../../utils/Utils';
12
13
 
13
14
  jest.mock('../../../iot/RemoteControl/Bluetooth');
14
15
  jest.mock('../../../iot/RemoteControl/HomeAssistant');
@@ -112,6 +113,47 @@ describe('Test useRemoteControl', () => {
112
113
  expect(sendCommandOverHomeAssistant).not.toBeCalled();
113
114
  });
114
115
 
116
+ const testCommandViaBluetooth = async (
117
+ command_prefer_over_bluetooth,
118
+ command_prefer_over_internet
119
+ ) => {
120
+ action.command_prefer_over_bluetooth = command_prefer_over_bluetooth;
121
+ action.command_prefer_over_internet = command_prefer_over_internet;
122
+ action.is_only_bluetooth = true;
123
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
124
+
125
+ const { result: sendRemoteCommand } = renderHook(() => useRemoteControl(), {
126
+ wrapper,
127
+ });
128
+
129
+ sendCommandOverBluetooth.mockImplementation(() => {
130
+ throw SEND_COMMAND_OVER_BLUETOOTH_FAIL;
131
+ });
132
+ sendCommandOverInternet.mockImplementation(async () => true);
133
+ await act(async () => {
134
+ await sendRemoteCommand.current(sensor, action, data, userId);
135
+ });
136
+ expect(sendCommandOverBluetooth).toBeCalledWith(
137
+ sensor,
138
+ action,
139
+ data,
140
+ userId
141
+ );
142
+ expect(sendCommandOverInternet).not.toBeCalled();
143
+ expect(sendCommandOverHomeAssistant).not.toBeCalled();
144
+ expect(spyToastError).toBeCalled();
145
+ };
146
+
147
+ // eslint-disable-next-line max-len
148
+ it('test send remote command via bluetooth failed then not send via internet, action is only bluetooth, not prefer internet', async () => {
149
+ testCommandViaBluetooth(true, false);
150
+ });
151
+
152
+ // eslint-disable-next-line max-len
153
+ it('test send remote command via bluetooth failed then not send via internet, action is only bluetooth, prefer internet', async () => {
154
+ testCommandViaBluetooth(false, true);
155
+ });
156
+
115
157
  it('test send remote command via bluetooth failed throw unhandled error', async () => {
116
158
  const { result: sendRemoteCommand } = renderHook(() => useRemoteControl(), {
117
159
  wrapper,
@@ -8,6 +8,7 @@ import {
8
8
  } from '../../iot/RemoteControl/Bluetooth';
9
9
  import { ToastBottomHelper } from '../../utils/Utils';
10
10
  import { t } from 'i18n-js';
11
+ import NetInfo from '@react-native-community/netinfo';
11
12
 
12
13
  const useRemoteControl = () => {
13
14
  const homeAssistantConnections = useSCContextSelector(
@@ -29,20 +30,38 @@ const useRemoteControl = () => {
29
30
  try {
30
31
  return await sendCommandOverBluetooth(device, action, data, userId);
31
32
  } catch (err) {
32
- if (err === SEND_COMMAND_OVER_BLUETOOTH_FAIL) {
33
- result = await sendCommandOverInternet(
34
- device,
35
- action,
36
- data,
37
- 'bluetooth'
38
- );
39
- return result;
33
+ const netState = await NetInfo.fetch();
34
+ if (netState.isConnected) {
35
+ result = false;
36
+
37
+ if (err === SEND_COMMAND_OVER_BLUETOOTH_FAIL) {
38
+ // Checking only bluetooth: not force internet
39
+ if (!action.is_only_bluetooth) {
40
+ result = await sendCommandOverInternet(
41
+ device,
42
+ action,
43
+ data,
44
+ 'bluetooth'
45
+ );
46
+ }
47
+ return result;
48
+ } else {
49
+ throw err;
50
+ }
40
51
  } else {
41
- throw err;
52
+ return false;
42
53
  }
43
54
  }
44
55
  }
45
56
 
57
+ // Checking only bluetooth: not other options
58
+ if (action.is_only_bluetooth) {
59
+ ToastBottomHelper.error(
60
+ t('this_command_is_just_support_bluetooth_only')
61
+ );
62
+ return result;
63
+ }
64
+
46
65
  if (action.command_prefer_over_internet) {
47
66
  return await sendCommandOverInternet(
48
67
  device,
@@ -140,7 +140,7 @@ export const sendDataOverBluetooth = async (
140
140
  let connectedDevice = null;
141
141
  let fullDataDevice = null;
142
142
  let result = true;
143
- const responseTime = 2000;
143
+
144
144
  try {
145
145
  connectedDevice = await device.connect();
146
146
  fullDataDevice =
@@ -148,15 +148,15 @@ export const sendDataOverBluetooth = async (
148
148
  await fullDataDevice.monitorCharacteristicForService(
149
149
  BLE.BLE_REMOTE_SERVICE_UUID,
150
150
  BLE.BLE_REMOTE_CHARACTERISTIC_UUID_TX,
151
- (error, characteristic) => {
151
+ async (error, characteristic) => {
152
152
  const notify = error ? '' : base64.decode(characteristic.value);
153
153
  if (notify === BLE.BLE_RESPONSE_OK) {
154
154
  ToastBottomHelper.success(
155
155
  t('control_device_via_bluetooth_successfully')
156
156
  );
157
157
  if (!keepConnect) {
158
- bleManager.cancelTransaction(BLE.BLE_LISTER_RESPONSE_CONTROL);
159
- device.cancelConnection();
158
+ await bleManager.cancelTransaction(BLE.BLE_LISTER_RESPONSE_CONTROL);
159
+ await bleManager.cancelDeviceConnection(device.id);
160
160
  }
161
161
  } else if (notify === BLE.BLE_RESPONSE_FAILED) {
162
162
  ToastBottomHelper.error(t('control_device_via_bluetooth_failed'));
@@ -179,10 +179,6 @@ export const sendDataOverBluetooth = async (
179
179
  return result;
180
180
  }
181
181
 
182
- const timeout = setTimeout(() => {
183
- device.cancelConnection();
184
- clearTimeout(timeout);
185
- }, responseTime);
186
182
  return result;
187
183
  };
188
184
 
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from 'react';
1
+ import React, { useCallback, useState } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import ActionGroup from '../../../commons/ActionGroup';
4
4
  import { CardDevMode } from '../../../commons/DevMode';
@@ -46,23 +46,29 @@ export const SensorDisplayItem = ({
46
46
  const { isEditingTemplate } = useSCContextSelector((state) => state.devMode);
47
47
 
48
48
  const sendRemoteCommand = useRemoteControl();
49
+ const [processing, setProcessing] = useState(false);
49
50
 
50
51
  const doAction = useCallback(
51
- (action, data) => {
52
- sendRemoteCommand(
52
+ async (action, data) => {
53
+ if (processing) {
54
+ return;
55
+ }
56
+ setProcessing(true);
57
+ await sendRemoteCommand(
53
58
  isEditingTemplate ? action?.end_device : sensor,
54
59
  action,
55
60
  data,
56
61
  userId
57
62
  );
63
+ setProcessing(false);
58
64
  },
59
- [isEditingTemplate, sensor, sendRemoteCommand, userId]
65
+ [processing, sendRemoteCommand, isEditingTemplate, sensor, userId]
60
66
  );
61
67
 
62
68
  if (type === 'compass') {
63
69
  setShowWindDirection && setShowWindDirection(true);
64
70
  }
65
- switch (item.type) {
71
+ switch (item.template) {
66
72
  case 'camera':
67
73
  return (
68
74
  <CardDevMode title={t('camera')} id={idTemplate} isWrap>
@@ -41,7 +41,7 @@ describe('Test EditTemplate', () => {
41
41
  const data = [
42
42
  {
43
43
  id: 1,
44
- type: 'camera',
44
+ template: 'camera',
45
45
  configuration: {
46
46
  uri: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
47
47
  },
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { FlatList, TouchableOpacity } from 'react-native';
4
4
  import { act, create } from 'react-test-renderer';
5
5
  import Template from '..';
6
+ import { Search } from '../../../commons/DevMode';
6
7
  import { API } from '../../../configs';
7
8
  import api from '../../../utils/Apis/axios';
8
9
 
@@ -39,4 +40,47 @@ describe('Test Template screen', () => {
39
40
  await TouchableOpacities[0].props.onPress();
40
41
  expect(mockNavigate).toBeCalled();
41
42
  });
43
+
44
+ it('Test onSearch', async () => {
45
+ mock.onGet(API.DEV_MODE.DASHBOARD.GET_TEMPLATES).reply(200, [
46
+ {
47
+ id: 1,
48
+ name: 'Template1',
49
+ description: 'test',
50
+ },
51
+ {
52
+ id: 1,
53
+ name: 'Template2',
54
+ description: 'test2',
55
+ },
56
+ ]);
57
+ await act(async () => {
58
+ tree = await create(<Template />);
59
+ });
60
+ const instance = tree.root;
61
+ const search = instance.findByType(Search);
62
+ await act(async () => {
63
+ await search.props.onSearch('Template1');
64
+ });
65
+ const flatList = instance.findByType(FlatList);
66
+ expect(flatList.props.data).toEqual([
67
+ { description: 'test', id: 1, name: 'Template1' },
68
+ ]);
69
+
70
+ await act(async () => {
71
+ await search.props.onSearch('');
72
+ });
73
+ expect(flatList.props.data).toEqual([
74
+ {
75
+ id: 1,
76
+ name: 'Template1',
77
+ description: 'test',
78
+ },
79
+ {
80
+ id: 1,
81
+ name: 'Template2',
82
+ description: 'test2',
83
+ },
84
+ ]);
85
+ });
42
86
  });
@@ -184,7 +184,7 @@ const TemplateDetail = () => {
184
184
  return;
185
185
  }
186
186
 
187
- const _data = _item?.configuration?.configs.map((config) => {
187
+ const _data = (_item?.configuration?.configs || []).map((config) => {
188
188
  const configValue = configValues[config.id]?.value;
189
189
  if ([null, undefined, NaN].includes(configValue)) {
190
190
  return;
@@ -22,6 +22,7 @@ const Template = () => {
22
22
  const [data, setData] = useState([]);
23
23
  const { navigate } = useNavigation();
24
24
  const isFocused = useIsFocused();
25
+ const [isRefreshing, setIsRefreshing] = useState(false);
25
26
 
26
27
  const onSearch = useCallback((value) => {
27
28
  if (value === '') {
@@ -42,6 +43,7 @@ const Template = () => {
42
43
  );
43
44
 
44
45
  const getTemplates = useCallback(async () => {
46
+ setIsRefreshing(true);
45
47
  const { success, data: dataDashboard } = await axiosGet(
46
48
  API.DEV_MODE.DASHBOARD.GET_TEMPLATES
47
49
  );
@@ -49,6 +51,7 @@ const Template = () => {
49
51
  setData(dataDashboard || []);
50
52
  arrTemplates = dataDashboard;
51
53
  }
54
+ setIsRefreshing(false);
52
55
  }, []);
53
56
 
54
57
  const renderListEmptyComponent = useMemo(() => {
@@ -84,6 +87,9 @@ const Template = () => {
84
87
  extraData={data}
85
88
  ListEmptyComponent={renderListEmptyComponent}
86
89
  numColumns={2}
90
+ showsVerticalScrollIndicator={false}
91
+ refreshing={isRefreshing}
92
+ onRefresh={getTemplates}
87
93
  />
88
94
  </View>
89
95
  </TouchableWithoutFeedback>
@@ -75,7 +75,6 @@ const parseErrorResponse = async (error) => {
75
75
  if (message?.message) {
76
76
  message = message.message;
77
77
  }
78
- message = `${firstKey}: ${message}`;
79
78
  } else {
80
79
  message = error.data || error.problem;
81
80
  }
@@ -1106,5 +1106,6 @@
1106
1106
  "text_alert_modal_popup_ct": "It’s a critical action and cannot be undone.",
1107
1107
  "text_understand_modal_popup_ct": "I fully understand that this action is critical and will lead to data loss.",
1108
1108
  "cannot_find_device_wifi": "Cannot find Device's Wifi",
1109
+ "this_command_is_just_support_bluetooth_only": "This command is just support bluetooth only",
1109
1110
  "photo_request_permission_des": "To select photo from gallery, please unlock access photo permission"
1110
1111
  }
@@ -1105,5 +1105,6 @@
1105
1105
  "text_working_hard": "Chúng tôi đang làm việc chăm chỉ để làm cho điều này hoạt động",
1106
1106
  "photo_request_permission": "Quyền yêu cầu ảnh",
1107
1107
  "cannot_find_device_wifi": "Không thể tìm thấy Wifi của thiết bị",
1108
+ "this_command_is_just_support_bluetooth_only": "Lệnh gửi này chỉ hỗ trợ bluetooth",
1108
1109
  "photo_request_permission_des": "Để chọn ảnh từ thư viện, vui lòng mở khóa quyền truy cập ảnh"
1109
1110
  }