@eohjsc/react-native-smart-city 0.7.27 → 0.7.31

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 (71) hide show
  1. package/index.js +2 -0
  2. package/package.json +2 -1
  3. package/src/commons/Dashboard/MyDashboardDevice/__test__/index.test.js +68 -0
  4. package/src/commons/Dashboard/MyDashboardDevice/index.js +46 -11
  5. package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +43 -11
  6. package/src/commons/Dashboard/MyUnit/index.js +40 -32
  7. package/src/commons/ModalAlert/index.js +51 -0
  8. package/src/commons/ModalAlert/styles.js +54 -0
  9. package/src/commons/SubUnit/ShortDetail.js +20 -4
  10. package/src/commons/SubUnit/__test__/ShortDetail.test.js +46 -1
  11. package/src/configs/API.js +8 -0
  12. package/src/configs/AccessibilityLabel.js +3 -0
  13. package/src/configs/Constants.js +7 -0
  14. package/src/configs/SCConfig.js +6 -0
  15. package/src/context/SCContext.tsx +12 -1
  16. package/src/context/SCStore.ts +14 -0
  17. package/src/context/actionType.ts +10 -0
  18. package/src/context/mockStore.ts +30 -1
  19. package/src/context/reducer.ts +35 -0
  20. package/src/hooks/IoT/useRemoteControl.js +4 -1
  21. package/src/hooks/IoT/useWatchSharedChips.js +130 -0
  22. package/src/hooks/Review/__test__/useInAppReview.test.js +99 -0
  23. package/src/hooks/Review/useInAppReview.js +70 -0
  24. package/src/hooks/useMqtt.js +78 -27
  25. package/src/iot/Monitor.js +149 -26
  26. package/src/iot/UpdateStates.js +60 -0
  27. package/src/iot/mqtt.js +177 -22
  28. package/src/navigations/UnitStack.js +16 -0
  29. package/src/screens/ActivityLog/ItemLog.js +1 -0
  30. package/src/screens/AddNewGateway/RenameNewDevices.js +5 -0
  31. package/src/screens/AddNewGateway/__test__/RenameNewDevices.test.js +18 -0
  32. package/src/screens/Automate/AddNewAction/ReceiverSelect.js +210 -0
  33. package/src/screens/Automate/AddNewAction/SetupScriptEmail.js +1 -1
  34. package/src/screens/Automate/AddNewAction/SetupScriptNotify.js +18 -28
  35. package/src/screens/Automate/AddNewAction/SetupScriptReceiverEmail.js +22 -129
  36. package/src/screens/Automate/AddNewAction/SetupScriptReceiverNotify.js +59 -0
  37. package/src/screens/Automate/AddNewAction/SetupScriptReceiverSms.js +22 -129
  38. package/src/screens/Automate/AddNewAction/SetupScriptSms.js +1 -1
  39. package/src/screens/Automate/AddNewAction/Styles/{SetupScriptReceiverEmailStyles.js → ReceiverSelectStyles.js} +18 -1
  40. package/src/screens/Automate/AddNewAction/__test__/SetupScriptNotify.test.js +16 -33
  41. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverEmail.test.js +10 -8
  42. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverNotify.test.js +217 -0
  43. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverSms.test.js +10 -8
  44. package/src/screens/Automate/Components/InputName.js +5 -1
  45. package/src/screens/Automate/EditActionsList/UpdateReceiverEmailScript.js +4 -3
  46. package/src/screens/Automate/EditActionsList/UpdateReceiverSmsScript.js +5 -4
  47. package/src/screens/Automate/OneTap/__test__/AddNewOneTap.test.js +18 -0
  48. package/src/screens/Automate/ScriptDetail/Styles/indexStyles.js +1 -1
  49. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +116 -2
  50. package/src/screens/Automate/ScriptDetail/index.js +47 -9
  51. package/src/screens/CreatePassword/__test__/index.test.js +133 -0
  52. package/src/screens/CreatePassword/index.js +134 -0
  53. package/src/screens/CreatePassword/styles.js +45 -0
  54. package/src/screens/Device/__test__/DeviceDetail-3rdparty.test.js +447 -0
  55. package/src/screens/Device/__test__/DeviceDetail-arduino.test.js +344 -0
  56. package/src/screens/Device/__test__/{mqttDetail.test.js → DeviceDetail-modbus.test.js} +287 -320
  57. package/src/screens/Device/__test__/DeviceDetail-zigbee.test.js +451 -0
  58. package/src/screens/Device/__test__/DeviceDetail.test.js +502 -0
  59. package/src/screens/Device/__test__/detail.test.js +61 -3
  60. package/src/screens/Device/__test__/sensorDisplayItem.test.js +28 -3
  61. package/src/screens/Device/detail.js +14 -6
  62. package/src/screens/Device/hooks/useDeviceWatchConfigControl.js +3 -2
  63. package/src/screens/EnterPassword/__test__/EnterPassword.test.js +76 -1
  64. package/src/screens/EnterPassword/index.js +34 -4
  65. package/src/screens/EnterPassword/styles.js +1 -1
  66. package/src/utils/FactoryGateway.js +597 -0
  67. package/src/utils/I18n/translations/en.js +11 -0
  68. package/src/utils/I18n/translations/vi.js +11 -0
  69. package/src/utils/Route/index.js +3 -1
  70. package/src/utils/Validation.js +5 -0
  71. package/src/utils/store.js +5 -0
@@ -1,13 +1,17 @@
1
1
  import React from 'react';
2
+ import MockAdapter from 'axios-mock-adapter';
2
3
  import { Image, View } from 'react-native';
3
4
  import mock from 'react-native-permissions/mock';
4
5
  import { act, create } from 'react-test-renderer';
6
+ import { API } from '../../../configs';
5
7
  import { AccessibilityLabel } from '../../../configs/Constants';
6
8
  import { SCProvider } from '../../../context';
7
9
  import { mockSCStore } from '../../../context/mockStore';
8
10
  import { watchMultiConfigs } from '../../../iot/Monitor';
9
11
  import { keyPermission } from '../../../utils/Permission/common';
12
+ import api from '../../../utils/Apis/axios';
10
13
  import Routes from '../../../utils/Route';
14
+ import { gatewayDataFactory } from '../../../utils/FactoryGateway';
11
15
  import ItemHanetDevice from '../../Device/Hanet/ItemHanetDevice';
12
16
  import ItemAddNew from '../../Device/ItemAddNew';
13
17
  import ItemDevice from '../../Device/ItemDevice';
@@ -15,6 +19,8 @@ import Text from '../../Text';
15
19
  import { DeviceTemplate } from '../DeviceTemplate/DeviceTemplate';
16
20
  import ShortDetailSubUnit from '../ShortDetail';
17
21
 
22
+ const mockAxios = new MockAdapter(api.axiosInstance);
23
+
18
24
  jest.mock('../../../iot/Monitor');
19
25
 
20
26
  const wrapComponent = (props) => (
@@ -32,7 +38,9 @@ jest.mock('../../../iot/states', () => ({
32
38
  }));
33
39
 
34
40
  describe('test ShortDetail Subunit', () => {
35
- let tree, props;
41
+ let tree;
42
+ let props;
43
+
36
44
  beforeEach(() => {
37
45
  jest.useFakeTimers();
38
46
  props = {
@@ -69,6 +77,7 @@ describe('test ShortDetail Subunit', () => {
69
77
  isNetworkConnected: true,
70
78
  isGGHomeConnected: true,
71
79
  };
80
+ watchMultiConfigs.mockClear();
72
81
  });
73
82
 
74
83
  it('render ShortDetail', async () => {
@@ -182,6 +191,42 @@ describe('test ShortDetail Subunit', () => {
182
191
  expect(watchMultiConfigs).toHaveBeenCalledWith([1]);
183
192
  });
184
193
 
194
+ it('render using mqtt not call watch config', async () => {
195
+ mockAxios
196
+ .onGet(API.CHIP.JSON_CONFIGURATION)
197
+ .reply(200, [gatewayDataFactory]);
198
+ props.station.devices = [
199
+ {
200
+ action: {
201
+ color: '#00979D',
202
+ icon: 'caretup',
203
+ id: 1,
204
+ key: '',
205
+ },
206
+ action2: null,
207
+ chip_id: 7689,
208
+ description: null,
209
+ icon: '',
210
+ id: 1,
211
+ name: 'People Counting',
212
+ quick_action: { config_id: 128282 },
213
+ remote_control_options: {},
214
+ station: {},
215
+ status: null,
216
+ status2: null,
217
+ is_managed_by_backend: true,
218
+ },
219
+ ];
220
+
221
+ await act(async () => {
222
+ tree = await create(wrapComponent(props));
223
+ });
224
+ await act(async () => {
225
+ jest.runOnlyPendingTimers();
226
+ });
227
+ expect(watchMultiConfigs).not.toHaveBeenCalled();
228
+ });
229
+
185
230
  ['ConfigAndEvaluation', 'ConfigValue', 'EvaluationOverConfig'].forEach(
186
231
  (template) => {
187
232
  it(`render with device template ${template}`, async () => {
@@ -2,8 +2,12 @@ import { SCConfig } from './SCConfig';
2
2
 
3
3
  const API = {
4
4
  AUTH: {
5
+ CREATE_PASSWORD: '/accounts/create_password/',
5
6
  PERMISSIONS: () => '/accounts/permissions/',
6
7
  },
8
+ ACCOUNTS: {
9
+ USABLE_PASSWORD: '/accounts/usable_password/',
10
+ },
7
11
  UNIT: {
8
12
  MY_UNITS: () => '/property_manager/units/mine/',
9
13
  SHARED_UNITS: () => '/property_manager/shared_units/',
@@ -65,6 +69,7 @@ const API = {
65
69
  `/iot/modules/zigbee/chips/${id}/device_permit_to_join/`,
66
70
  },
67
71
  JSON_CONFIGURATION: '/chip_manager/chips/json_configuration/',
72
+ SHARED_CONFIGURATION: '/chip_manager/chips/shared_configuration/',
68
73
  },
69
74
  DEVICE: {
70
75
  DEVICE_DETAIL: (id) => `/property_manager/devices/${id}/`,
@@ -144,6 +149,8 @@ const API = {
144
149
  CREATE_AUTOMATE_V2: () => '/property_manager/automate_v2/',
145
150
  UPDATE_AUTOMATE: (automateId) =>
146
151
  `/property_manager/automate/${automateId}/`,
152
+ ENABLE_NOTIFICATIONS: (automateId) =>
153
+ `/property_manager/automate/${automateId}/enable_notifications/`,
147
154
  ENABLE_SCRIPT: (automateId) =>
148
155
  `/property_manager/automate/${automateId}/enable_script/`,
149
156
  STAR_SCRIPT: (id) => `/property_manager/automate/${id}/star_script/`,
@@ -203,6 +210,7 @@ const API = {
203
210
  IOT: {
204
211
  CHIP_MANAGER: {
205
212
  WATCH_CONFIGS: () => '/chip_manager/watch_configs_v2/',
213
+ WATCH_DATA_CHIPS: () => '/chip_manager/watch_data_chips/',
206
214
  PUSHER_AUTH: () => '/chip_manager/pusher/auth_v2/',
207
215
  },
208
216
  LG: {
@@ -164,6 +164,7 @@ export default {
164
164
  //Enter Password
165
165
  ENTER_PASSWORD_TEXT_INPUT_PASSWORD: 'ENTER_PASSWORD_TEXT_INPUT_PASSWORD',
166
166
  ENTER_PASSWORD_BUTTON_DONE: 'ENTER_PASSWORD_BUTTON_DONE',
167
+ CREATE_PASSWORD_BUTTON_DONE: 'CREATE_PASSWORD_BUTTON_DONE',
167
168
 
168
169
  // Map Dashboard
169
170
  PARKING_MARKER: 'PARKING_MARKER',
@@ -261,6 +262,8 @@ export default {
261
262
  AUTOMATE_SELECT_CONDITION: 'AUTOMATE_SELECT_CONDITION',
262
263
  AUTOMATE_DELETE_CONDITION: 'AUTOMATE_DELETE_CONDITION',
263
264
  AUTOMATE_NUMBER_CONDITION: 'AUTOMATE_NUMBER_CONDITION',
265
+ SWITCH_ENABLE_SCRIPT: 'SWITCH_ENABLE_SCRIPT',
266
+ SWITCH_ENABLE_NOTIFICATIONS_SCRIPT: 'SWITCH_ENABLE_NOTIFICATIONS_SCRIPT',
264
267
 
265
268
  // Parking input maunaly spot
266
269
  PARKING_SPOT_INFO_BUTTON: 'PARKING_SPOT_INFO_BUTTON',
@@ -294,3 +294,10 @@ export const WIDGET_TYPE = {
294
294
  iframe: 'IFrame',
295
295
  iframeWithConfig: 'IFrameWithConfig',
296
296
  };
297
+
298
+ export const WATCH_DATA_CHIP_TYPE = {
299
+ MODBUS: 'modbus',
300
+ ZIGBEE: 'zigbee',
301
+ THIRD_PARTY: 'third_party',
302
+ SELF: 'self',
303
+ };
@@ -99,6 +99,7 @@ const SCDefaultConfig = {
99
99
  pusherAppKey: '8557fcc63959f564f1aa',
100
100
  pusherAppCluster: 'ap1',
101
101
  intervalWatchConfigTime: 30000,
102
+ intervalWatchervalWatchDataChipTime: 30000,
102
103
  setCurrentSensorDisplay: () => {},
103
104
  appName: 'E-Ra',
104
105
  appLogo: Images.logo,
@@ -118,6 +119,8 @@ export class SCConfig {
118
119
  static pusherAppCluste = SCDefaultConfig.pusherAppCluster;
119
120
  static language = 'en';
120
121
  static intervalWatchConfigTime = SCDefaultConfig.intervalWatchConfigTime;
122
+ static intervalWatchervalWatchDataChipTime =
123
+ SCDefaultConfig.intervalWatchervalWatchDataChipTime;
121
124
  static setCurrentSensorDisplay = SCDefaultConfig.setCurrentSensorDisplay;
122
125
  static appName = SCDefaultConfig.appName;
123
126
  static appLogo = SCDefaultConfig.appLogo;
@@ -150,6 +153,9 @@ export const initSCConfig = (config) => {
150
153
  config.pusherAppCluster ?? SCDefaultConfig.pusherAppCluster;
151
154
  SCConfig.intervalWatchConfigTime =
152
155
  config.intervalWatchConfigTime ?? SCDefaultConfig.intervalWatchConfigTime;
156
+ SCConfig.intervalWatchervalWatchDataChipTime =
157
+ config.intervalWatchervalWatchDataChipTime ??
158
+ SCDefaultConfig.intervalWatchervalWatchDataChipTime;
153
159
  SCConfig.setCurrentSensorDisplay =
154
160
  config.setCurrentSensorDisplay ?? SCDefaultConfig.setCurrentSensorDisplay;
155
161
  SCConfig.appName = config.appName ?? SCDefaultConfig.appName;
@@ -1,4 +1,4 @@
1
- import React, { useContext, useReducer } from 'react';
1
+ import React, { useContext, useEffect, useRef, useReducer } from 'react';
2
2
  import { StyleSheet, View } from 'react-native';
3
3
  import Toast from 'react-native-toast-message';
4
4
 
@@ -10,6 +10,7 @@ import {
10
10
  StatusBar,
11
11
  } from './actionType';
12
12
  import { initialState, Action, ContextData, reducer } from './reducer';
13
+ import { setSCStoreGetter } from './SCStore';
13
14
  import { setConfigGlobalState } from '../iot/states.js';
14
15
  import { setAxiosDefaultLanguage } from '../utils/Utils';
15
16
  import { Alert } from '../commons';
@@ -50,6 +51,16 @@ export const SCProvider = ({ children, initState = initialState }) => {
50
51
  initState
51
52
  );
52
53
 
54
+ const stateRef = useRef<ContextData>(stateData);
55
+ useEffect(() => {
56
+ stateRef.current = stateData;
57
+ }, [stateData]);
58
+
59
+ useEffect(() => {
60
+ setSCStoreGetter(() => stateRef.current);
61
+ return () => setSCStoreGetter(null);
62
+ }, []);
63
+
53
64
  const setAuth = (authData: AuthData) => {
54
65
  setAction('UPDATE_AUTH', authData);
55
66
  };
@@ -0,0 +1,14 @@
1
+ import type { ContextData } from './reducer';
2
+
3
+ let getStateFn: (() => ContextData) | null = null;
4
+
5
+ export const setSCStoreGetter = (getter: () => ContextData) => {
6
+ getStateFn = getter;
7
+ };
8
+
9
+ export const getSCState = (): ContextData => {
10
+ if (!getStateFn) {
11
+ throw new Error('SC store is not ready');
12
+ }
13
+ return getStateFn();
14
+ };
@@ -29,6 +29,8 @@ export const Action = {
29
29
  SET_HOME_ASSISTANT_CONNECTIONS: 'SET_HOME_ASSISTANT_CONNECTIONS',
30
30
  CHANGE_HOME_ASSISTANT_CONN_STATE: 'CHANGE_HOME_ASSISTANT_CONN_STATE',
31
31
  SET_LG_THINQ_CONNECTED: 'SET_LG_THINQ_CONNECTED',
32
+ UPDATE_CONFIGS_BY_ID: 'UPDATE_CONFIGS_BY_ID',
33
+ UPDATE_SHARED_CHIPS_BY_ID: 'UPDATE_SHARED_CHIPS_BY_ID',
32
34
  UPDATE_VALUE_EVALUATIONS: 'UPDATE_VALUE_EVALUATIONS',
33
35
  INIT_VALUE_EVALUATIONS: 'INIT_VALUE_EVALUATIONS',
34
36
  NEED_UPDATE_VALUE_EVALUATIONS: 'NEED_UPDATE_VALUE_EVALUATIONS',
@@ -91,6 +93,14 @@ export type AutomateType = {
91
93
  isUpdateCondition: false;
92
94
  };
93
95
 
96
+ export type ChipType = {
97
+ sharedChipsById: {};
98
+ };
99
+
100
+ export type ConfigType = {
101
+ configsById: {};
102
+ };
103
+
94
104
  export type ActionType = keyof typeof Action;
95
105
 
96
106
  export type ActionDataMap = {
@@ -32,14 +32,27 @@ export const mockDataStore: ContextData = {
32
32
  },
33
33
  automate: {
34
34
  starredScriptIds: [],
35
+ isCreateCondition: false,
36
+ isUpdateCondition: false,
37
+ },
38
+ chip: {
39
+ sharedChipsById: {},
40
+ },
41
+ config: {
42
+ configsById: {},
35
43
  },
36
44
  app: {
37
45
  isFirstOpenCamera: true,
38
46
  isLavidaSource: false,
39
47
  isConnectWifiGateway: false,
40
- isNetworkConnected: true,
48
+ isNetworkConnected: false,
41
49
  camera_opened: [],
50
+ notificationData: null,
51
+ popoverAnimating: false,
42
52
  isLockWhenPickColor: false,
53
+ isNeedUpdateCache: false,
54
+ isDeleteUnitSuccessFully: false,
55
+ appState: 'active',
43
56
  },
44
57
  iot: {
45
58
  bluetooth: {
@@ -114,11 +127,27 @@ export const mockSCStore = (data: ContextData): ContextData => {
114
127
  ],
115
128
  },
116
129
  automate: {
130
+ ...mockDataStore.automate,
131
+ ...data?.automate,
117
132
  starredScriptIds: [
118
133
  ...mockDataStore.automate.starredScriptIds,
119
134
  ...(data?.automate?.starredScriptIds || []),
120
135
  ],
121
136
  },
137
+ chip: {
138
+ ...mockDataStore.chip,
139
+ sharedChipsById: {
140
+ ...mockDataStore.chip.sharedChipsById,
141
+ ...data?.chip?.sharedChipsById,
142
+ },
143
+ },
144
+ config: {
145
+ ...mockDataStore.config,
146
+ configsById: {
147
+ ...mockDataStore.config.configsById,
148
+ ...data?.config?.configsById,
149
+ },
150
+ },
122
151
  iot: {
123
152
  ...mockDataStore.iot,
124
153
  homeassistant: {
@@ -9,6 +9,8 @@ import {
9
9
  ListAction,
10
10
  UnitType,
11
11
  AutomateType,
12
+ ChipType,
13
+ ConfigType,
12
14
  AppType,
13
15
  IoTType,
14
16
  DevModeType,
@@ -25,6 +27,8 @@ export type ContextData = {
25
27
  statusBar: StatusBar;
26
28
  unit: UnitType;
27
29
  automate: AutomateType;
30
+ chip: ChipType;
31
+ config: ConfigType;
28
32
  app: AppType;
29
33
  iot: IoTType;
30
34
  valueEvaluations: {};
@@ -62,6 +66,12 @@ export const initialState = {
62
66
  automate: {
63
67
  starredScriptIds: [],
64
68
  },
69
+ chip: {
70
+ sharedChipsById: {},
71
+ },
72
+ config: {
73
+ configsById: {},
74
+ },
65
75
  app: {
66
76
  isFirstOpenCamera: true,
67
77
  isLavidaSource: false,
@@ -366,6 +376,31 @@ export const reducer = (currentState: ContextData, action: Action) => {
366
376
  isUpdateCondition: payload,
367
377
  },
368
378
  };
379
+
380
+ case Action.UPDATE_CONFIGS_BY_ID:
381
+ return {
382
+ ...currentState,
383
+ config: {
384
+ ...currentState.config,
385
+ configsById: {
386
+ ...currentState.config.configsById,
387
+ ...payload,
388
+ },
389
+ },
390
+ };
391
+
392
+ case Action.UPDATE_SHARED_CHIPS_BY_ID:
393
+ return {
394
+ ...currentState,
395
+ chip: {
396
+ ...currentState.chip,
397
+ sharedChipsById: {
398
+ ...currentState.chip.sharedChipsById,
399
+ ...payload,
400
+ },
401
+ },
402
+ };
403
+
369
404
  case Action.SET_BLUETOOTH_CONNECTED_DEVICE:
370
405
  return {
371
406
  ...currentState,
@@ -9,6 +9,7 @@ import {
9
9
  } from '../../iot/RemoteControl/Bluetooth';
10
10
  import { sendCommandOverHomeAssistant } from '../../iot/RemoteControl/HomeAssistant';
11
11
  import { sendCommandOverInternet } from '../../iot/RemoteControl/Internet';
12
+ import useInAppReview from '../Review/useInAppReview';
12
13
  import { ToastBottomHelper } from '../../utils/Utils';
13
14
 
14
15
  const handleReconnectionDeviceBle = async (device, action, data, userId) => {
@@ -30,6 +31,7 @@ const useRemoteControl = (bluetoothDevice) => {
30
31
  const homeAssistantConnections = useSCContextSelector(
31
32
  (state) => state.iot.homeassistant.connections
32
33
  );
34
+ const { incrementReviewTrigger } = useInAppReview(false);
33
35
 
34
36
  const sendRemoteCommand = useCallback(
35
37
  async (device, action, data, userId, silent = false) => {
@@ -41,6 +43,7 @@ const useRemoteControl = (bluetoothDevice) => {
41
43
  );
42
44
  return result;
43
45
  }
46
+ incrementReviewTrigger();
44
47
  if (action.command_prefer_over_bluetooth) {
45
48
  // custom control for i/o pin over bluetooth
46
49
  if (bluetoothDevice?.isConnected && action.arduino_action) {
@@ -106,7 +109,7 @@ const useRemoteControl = (bluetoothDevice) => {
106
109
 
107
110
  return await sendCommandOverInternet(device, action, data, 'internet');
108
111
  },
109
- [bluetoothDevice, homeAssistantConnections]
112
+ [bluetoothDevice, homeAssistantConnections, incrementReviewTrigger]
110
113
  );
111
114
 
112
115
  return sendRemoteCommand;
@@ -0,0 +1,130 @@
1
+ import {
2
+ useCallback,
3
+ useContext,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import { useIsFocused } from '@react-navigation/native';
10
+
11
+ import { API } from '../../configs';
12
+ import { Action } from '../../context/actionType';
13
+ import { SCContext, useSCContextSelector } from '../../context';
14
+ import { axiosGet } from '../../utils/Apis/axios';
15
+ import { unwatchMultiDataChips, watchMultiDataChips } from '../../iot/Monitor';
16
+ import { SCConfig } from '../../configs';
17
+
18
+ const useWatchSharedChips = ({ dashboardId, filterChipIds, ready = true }) => {
19
+ const isFocused = useIsFocused();
20
+ const { setAction } = useContext(SCContext);
21
+ const [chipIds, setChipIds] = useState([]);
22
+
23
+ const chipIdsToWatchRef = useRef([]);
24
+ const intervalWatchRef = useRef(null);
25
+
26
+ const isNetworkConnected = useSCContextSelector(
27
+ (state) => state.app.isNetworkConnected
28
+ );
29
+
30
+ const handleUpdateConfigsById = useCallback(
31
+ (chips) => {
32
+ const configsById = chips.reduce((acc, chip) => {
33
+ chip.sensors.forEach((sensor) => {
34
+ sensor.configs.forEach((config) => {
35
+ acc[config.id] = config;
36
+ });
37
+ });
38
+ return acc;
39
+ }, {});
40
+ setAction(Action.UPDATE_CONFIGS_BY_ID, configsById);
41
+ },
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ []
44
+ );
45
+
46
+ const handleUpdateSharedChipsById = useCallback(
47
+ (chips) => {
48
+ const sharedChipsById = chips.reduce((acc, chip) => {
49
+ acc[chip.id] = chip;
50
+ return acc;
51
+ }, {});
52
+ setAction(Action.UPDATE_SHARED_CHIPS_BY_ID, sharedChipsById);
53
+ },
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ []
56
+ );
57
+
58
+ useEffect(() => {
59
+ if (!isNetworkConnected) {
60
+ return;
61
+ }
62
+ const getGateways = async () => {
63
+ const { success, data } = await axiosGet(API.CHIP.SHARED_CONFIGURATION, {
64
+ params: {
65
+ unit: dashboardId,
66
+ },
67
+ });
68
+ if (success) {
69
+ setChipIds(data.map((chip) => chip.id));
70
+ handleUpdateConfigsById(data);
71
+ handleUpdateSharedChipsById(data);
72
+ }
73
+ };
74
+ ready && getGateways();
75
+ }, [
76
+ dashboardId,
77
+ ready,
78
+ isNetworkConnected,
79
+ handleUpdateConfigsById,
80
+ handleUpdateSharedChipsById,
81
+ ]);
82
+
83
+ const chipIdsToWatch = useMemo(() => {
84
+ if (Array.isArray(filterChipIds)) {
85
+ return chipIds.filter((chip) => filterChipIds.includes(chip));
86
+ }
87
+ return chipIds;
88
+ }, [chipIds, filterChipIds]);
89
+
90
+ const cleanupWatch = useCallback(() => {
91
+ if (intervalWatchRef.current) {
92
+ clearInterval(intervalWatchRef.current);
93
+ intervalWatchRef.current = null;
94
+ }
95
+ if (chipIdsToWatchRef.current.length) {
96
+ unwatchMultiDataChips(chipIdsToWatchRef.current);
97
+ chipIdsToWatchRef.current = [];
98
+ }
99
+ }, []);
100
+
101
+ useEffect(() => {
102
+ if (!isFocused) {
103
+ cleanupWatch();
104
+ return;
105
+ }
106
+ if (!chipIdsToWatch.length) {
107
+ return;
108
+ }
109
+
110
+ if (!chipIdsToWatchRef.current.length) {
111
+ watchMultiDataChips(chipIdsToWatch);
112
+ chipIdsToWatchRef.current = chipIdsToWatch;
113
+ }
114
+
115
+ if (intervalWatchRef.current) {
116
+ clearInterval(intervalWatchRef.current);
117
+ }
118
+ intervalWatchRef.current = setInterval(() => {
119
+ watchMultiDataChips(chipIdsToWatch);
120
+ }, SCConfig.intervalWatchervalWatchDataChipTime);
121
+ }, [isFocused, chipIdsToWatch, cleanupWatch]);
122
+
123
+ useEffect(() => {
124
+ return () => {
125
+ cleanupWatch();
126
+ };
127
+ }, [cleanupWatch]);
128
+ };
129
+
130
+ export default useWatchSharedChips;
@@ -0,0 +1,99 @@
1
+ import { act, renderHook } from '@testing-library/react-hooks';
2
+ import InAppReview from 'react-native-in-app-review';
3
+ import { getObject, storeObject } from '../../../utils/Storage';
4
+
5
+ import useInAppReview from '../useInAppReview';
6
+
7
+ jest.mock('../../../utils/Storage', () => {
8
+ return {
9
+ ...jest.requireActual('../../../utils/Storage'),
10
+ getObject: jest.fn(),
11
+ storeObject: jest.fn(),
12
+ };
13
+ });
14
+
15
+ describe('Test useInAppReview', () => {
16
+ const mockOnFailed = jest.fn();
17
+
18
+ beforeEach(() => {
19
+ Date.now = jest.fn(() => new Date('2025-09-09T10:00:00.00Z'));
20
+ getObject.mockImplementation(() => ({ allow: true }));
21
+ storeObject.mockClear();
22
+ mockOnFailed.mockClear();
23
+ });
24
+
25
+ it('test calls RequestInAppReview and updates cache when allowed', async () => {
26
+ await act(async () => {
27
+ renderHook(() => useInAppReview(true, mockOnFailed));
28
+ });
29
+ expect(storeObject).toHaveBeenCalledWith('in_app_review', {
30
+ allow: true,
31
+ count: 1,
32
+ last: new Date('2025-09-09T10:00:00.00Z'),
33
+ });
34
+ expect(InAppReview.RequestInAppReview).toHaveBeenCalled();
35
+ expect(mockOnFailed).not.toHaveBeenCalled();
36
+ });
37
+
38
+ it('test updates cache even when RequestInAppReview rejects', async () => {
39
+ InAppReview.RequestInAppReview.mockRejectedValueOnce(new Error('fail'));
40
+ await act(async () => {
41
+ renderHook(() => useInAppReview(true, mockOnFailed));
42
+ });
43
+ expect(storeObject).toHaveBeenCalledWith('in_app_review', {
44
+ allow: true,
45
+ count: 1,
46
+ last: new Date('2025-09-09T10:00:00.00Z'),
47
+ });
48
+ expect(InAppReview.RequestInAppReview).toHaveBeenCalled();
49
+ expect(mockOnFailed).toHaveBeenCalled();
50
+ });
51
+
52
+ it('test does nothing when InAppReview is not available', async () => {
53
+ InAppReview.isAvailable.mockReturnValueOnce(false);
54
+ await act(async () => {
55
+ renderHook(() => useInAppReview(true, mockOnFailed));
56
+ });
57
+ expect(storeObject).toHaveBeenCalledWith('in_app_review', {
58
+ allow: true,
59
+ count: 1,
60
+ last: new Date('2025-09-09T10:00:00.00Z'),
61
+ });
62
+ expect(InAppReview.RequestInAppReview).not.toHaveBeenCalled();
63
+ expect(mockOnFailed).toHaveBeenCalled();
64
+ });
65
+
66
+ it('test does nothing when max prompts reached', async () => {
67
+ getObject.mockImplementation(() => ({ allow: true, count: 3 }));
68
+ await act(async () => {
69
+ renderHook(() => useInAppReview(true, mockOnFailed));
70
+ });
71
+ expect(storeObject).not.toHaveBeenCalled();
72
+ expect(InAppReview.RequestInAppReview).not.toHaveBeenCalled();
73
+ expect(mockOnFailed).not.toHaveBeenCalled();
74
+ });
75
+
76
+ it('test does nothing when still in retry interval', async () => {
77
+ getObject.mockImplementation(() => ({
78
+ allow: true,
79
+ count: 0,
80
+ last: new Date('2025-09-06T10:00:01.00Z'),
81
+ }));
82
+ await act(async () => {
83
+ renderHook(() => useInAppReview(true, mockOnFailed));
84
+ });
85
+ expect(storeObject).not.toHaveBeenCalled();
86
+ expect(InAppReview.RequestInAppReview).not.toHaveBeenCalled();
87
+ expect(mockOnFailed).not.toHaveBeenCalled();
88
+ });
89
+
90
+ it('test does nothing when first time and allow = false', async () => {
91
+ getObject.mockImplementation(() => ({}));
92
+ await act(async () => {
93
+ renderHook(() => useInAppReview(true, mockOnFailed));
94
+ });
95
+ expect(storeObject).not.toHaveBeenCalled();
96
+ expect(InAppReview.RequestInAppReview).not.toHaveBeenCalled();
97
+ expect(mockOnFailed).not.toHaveBeenCalled();
98
+ });
99
+ });