@eohjsc/react-native-smart-city 0.3.92 → 0.3.94

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 (87) hide show
  1. package/package.json +5 -1
  2. package/src/commons/ActionGroup/SliderRangeTemplate.js +2 -2
  3. package/src/commons/AlertAction/index.js +5 -0
  4. package/src/commons/BottomButtonView/index.js +22 -4
  5. package/src/commons/Button/index.js +5 -0
  6. package/src/commons/Device/ConnectedViewHeader.js +0 -1
  7. package/src/commons/Device/Emergency/EmergencyDetail.js +4 -2
  8. package/src/commons/Device/Emergency/__test__/EmergencyDetail.test.js +4 -2
  9. package/src/commons/Device/ProgressBar/index.js +3 -1
  10. package/src/commons/Device/ProgressBar/styles.js +1 -4
  11. package/src/commons/Device/WindSpeed/Anemometer/index.js +1 -1
  12. package/src/commons/Header/HeaderCustom.js +8 -18
  13. package/src/commons/SubUnit/OneTap/ItemOneTap.js +84 -129
  14. package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +74 -39
  15. package/src/commons/SubUnit/OneTap/index.js +24 -4
  16. package/src/commons/ViewButtonBottom/index.js +32 -4
  17. package/src/configs/API.js +4 -0
  18. package/src/configs/AccessibilityLabel.js +1 -0
  19. package/src/configs/BLE.js +1 -0
  20. package/src/context/actionType.ts +2 -1
  21. package/src/context/mockStore.ts +1 -0
  22. package/src/context/reducer.ts +12 -1
  23. package/src/hooks/Explore/useKeyboardAnimated.js +10 -4
  24. package/src/hooks/IoT/useBluetoothConnection.js +14 -26
  25. package/src/hooks/useMqtt.js +95 -0
  26. package/src/iot/Monitor.js +2 -1
  27. package/src/iot/RemoteControl/Bluetooth.js +56 -19
  28. package/src/iot/RemoteControl/__test__/Bluetooth.test.js +140 -0
  29. package/src/iot/mqtt.js +233 -0
  30. package/src/navigations/UnitStack.js +11 -3
  31. package/src/screens/AddLocationMaps/index.js +18 -16
  32. package/src/screens/AddLocationMaps/indexStyle.js +3 -0
  33. package/src/screens/AddNewGateway/ConnectingDevice.js +7 -0
  34. package/src/screens/AddNewGateway/RenameNewDevices.js +2 -2
  35. package/src/screens/AddNewGateway/SelectDeviceType.js +67 -9
  36. package/src/screens/AddNewGateway/__test__/ConnectingZigbeeDevice.test.js +35 -0
  37. package/src/screens/AddNewGateway/__test__/SelectDeviceType.test.js +183 -29
  38. package/src/screens/Automate/AddNewAction/NewActionWrapper.js +3 -6
  39. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +29 -29
  40. package/src/screens/Automate/AddNewAction/__test__/SetupSensor.test.js +45 -39
  41. package/src/screens/Automate/AddNewAutoSmart/AddAutomationTypeSmart.js +25 -0
  42. package/src/screens/{AddNewAutoSmart/index.js → Automate/AddNewAutoSmart/AddTypeSmart.js} +16 -51
  43. package/src/screens/Automate/AddNewAutoSmart/AddUnknownTypeSmart.js +29 -0
  44. package/src/screens/{AddNewAutoSmart → Automate/AddNewAutoSmart}/__test__/AddNewAutoSmart.test.js +11 -11
  45. package/src/screens/{AddNewAutoSmart → Automate/AddNewAutoSmart}/styles/AddNewAutoSmartStyles.js +1 -1
  46. package/src/screens/Automate/Components/InputNameStyles.js +1 -1
  47. package/src/screens/Automate/EditActionsList/__tests__/index.test.js +11 -25
  48. package/src/screens/Automate/EditActionsList/index.js +32 -33
  49. package/src/screens/Automate/MultiUnits.js +29 -19
  50. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +78 -5
  51. package/src/screens/Automate/ScriptDetail/index.js +38 -10
  52. package/src/screens/Automate/Styles/MultiUnitsStyles.js +1 -1
  53. package/src/screens/Automate/__test__/MultiUnits.test.js +64 -7
  54. package/src/screens/Automate/__test__/index.test.js +45 -11
  55. package/src/screens/Automate/index.js +53 -38
  56. package/src/screens/Device/__test__/detail.test.js +1 -1
  57. package/src/screens/Device/__test__/mqttDetail.test.js +599 -0
  58. package/src/screens/Device/components/SensorDisplayItem.js +1 -7
  59. package/src/screens/Device/detail.js +64 -30
  60. package/src/screens/Device/hooks/__test__/useEvaluateValue.test.js +24 -1
  61. package/src/screens/Device/hooks/useDeviceWatchConfigControl.js +13 -3
  62. package/src/screens/SelectUnit/__test__/index.test.js +8 -13
  63. package/src/screens/Sharing/InfoMemberUnit.js +2 -2
  64. package/src/screens/Sharing/MemberList.js +28 -7
  65. package/src/screens/Sharing/__test__/InfoMemberUnit.test.js +32 -18
  66. package/src/screens/Sharing/__test__/MemberList.test.js +37 -4
  67. package/src/screens/SmartAccount/__test__/SmartAccount.test.js +8 -4
  68. package/src/screens/SmartAccount/index.js +8 -9
  69. package/src/screens/SmartAccount/style.js +8 -7
  70. package/src/screens/Unit/AddMenu.js +42 -19
  71. package/src/screens/Unit/Detail.js +4 -19
  72. package/src/screens/Unit/Summaries.js +6 -17
  73. package/src/screens/Unit/__test__/AddMenu.test.js +68 -15
  74. package/src/screens/Unit/__test__/Summaries.test.js +2 -2
  75. package/src/screens/Unit/components/AutomateScript/index.js +1 -1
  76. package/src/utils/FactoryGateway.js +525 -0
  77. package/src/utils/I18n/translations/en.js +1409 -0
  78. package/src/utils/I18n/translations/vi.js +1411 -0
  79. package/src/utils/I18n/translations.ts +2 -2
  80. package/src/utils/Permission/backend.js +7 -0
  81. package/src/utils/Route/index.js +2 -1
  82. package/src/utils/Utils.js +11 -0
  83. package/src/commons/Device/SensorConnectedStatus.js +0 -56
  84. package/src/commons/Device/__test__/SensorConnectedStatus.test.js +0 -29
  85. package/src/screens/Sharing/__test__/MemberList2.test.js +0 -74
  86. package/src/utils/I18n/translations/en.json +0 -1138
  87. package/src/utils/I18n/translations/vi.json +0 -1136
@@ -7,61 +7,84 @@ import AddDeviceIcon from '../../../assets/images/Popover/Dashboard/AddDevice.sv
7
7
  import SmartAccount from '../../../assets/images/Popover/Dashboard/SmartAccount.svg';
8
8
  import AddMemberIcon from '../../../assets/images/Popover/Dashboard/AddMember.svg';
9
9
  import { useNavigation } from '@react-navigation/native';
10
+ import { ToastBottomHelper } from '../../utils/Utils';
11
+ import { useBackendPermission } from '../../utils/Permission/backend';
10
12
 
11
13
  const AddMenu = memo(({ unit, afterItemClick, showAdd, setHideAdd }) => {
12
14
  const t = useTranslations();
13
15
  const navigation = useNavigation();
16
+ const permissions = useBackendPermission();
14
17
 
15
18
  const onItemClick = useCallback(
16
- ({ route: routeName, data }) => {
19
+ (item) => {
17
20
  setHideAdd(true);
18
21
  afterItemClick();
19
- routeName && navigation.navigate(routeName, data);
22
+ item.onClick && item.onClick();
20
23
  },
21
- [setHideAdd, afterItemClick, navigation]
24
+ [setHideAdd, afterItemClick]
22
25
  );
23
26
 
24
27
  const listItem = useMemo(() => {
25
28
  return [
26
29
  {
27
30
  id: 'add_sub_unit',
28
- route: Routes.AddSubUnitStack,
29
31
  text: t('sub_unit'),
30
32
  image: <AddSubUnitIcon width={43} height={43} />,
31
- data: {
32
- screen: Routes.AddSubUnit,
33
- params: { unit, isInsideUnit: true },
33
+ onClick: () => {
34
+ // 2 is station favorite and smart
35
+ if (unit.stations.length - 2 >= permissions.max_stations_per_unit) {
36
+ ToastBottomHelper.error(t('reach_max_stations_per_unit'));
37
+ return;
38
+ }
39
+ navigation.navigate(Routes.AddSubUnitStack, {
40
+ screen: Routes.AddSubUnit,
41
+ params: { unit, isInsideUnit: true },
42
+ });
34
43
  },
35
44
  },
36
45
  {
37
46
  id: 'add_member',
38
- route: Routes.AddMemberStack,
39
47
  text: t('member'),
40
48
  image: <AddMemberIcon width={43} height={43} />,
41
- data: { screen: Routes.SharingSelectPermission, params: { unit } },
49
+ onClick: () =>
50
+ navigation.navigate(Routes.AddMemberStack, {
51
+ screen: Routes.SharingSelectPermission,
52
+ params: { unit },
53
+ }),
42
54
  },
43
55
  {
44
56
  id: 'add_device',
45
- route: Routes.AddGatewayStack,
46
57
  text: t('device'),
47
58
  image: <AddDeviceIcon width={43} height={43} />,
48
- data: {
49
- screen: Routes.SelectDeviceType,
50
- params: { unit },
59
+ onClick: () => {
60
+ let hasPermission = false;
61
+ Object.keys(permissions).forEach((key) => {
62
+ if (key.includes('plug_and_play') && permissions[key]) {
63
+ hasPermission = true;
64
+ }
65
+ });
66
+ if (!hasPermission) {
67
+ ToastBottomHelper.error(t('not_support_plug_and_play'));
68
+ return;
69
+ }
70
+ navigation.navigate(Routes.AddGatewayStack, {
71
+ screen: Routes.SelectDeviceType,
72
+ params: { unit },
73
+ });
51
74
  },
52
75
  },
53
76
  {
54
77
  id: 'add_smart_account',
55
- route: Routes.SmartAccountStack,
56
78
  text: t('name_smart_account'),
57
79
  image: <SmartAccount width={43} height={43} />,
58
- data: {
59
- screen: Routes.SmartAccount,
60
- params: { unitId: unit?.id, unitName: unit?.name },
61
- },
80
+ onClick: () =>
81
+ navigation.navigate(Routes.SmartAccountStack, {
82
+ screen: Routes.SmartAccount,
83
+ params: { unitId: unit?.id, unitName: unit?.name },
84
+ }),
62
85
  },
63
86
  ];
64
- }, [t, unit]);
87
+ }, [t, unit, navigation, permissions]);
65
88
 
66
89
  const hideAddModal = useCallback(() => {
67
90
  setHideAdd(false);
@@ -2,11 +2,10 @@ import React, {
2
2
  useCallback,
3
3
  useEffect,
4
4
  useState,
5
- useRef,
6
5
  useContext,
7
6
  useMemo,
8
7
  } from 'react';
9
- import { AppState, RefreshControl, View, Platform } from 'react-native';
8
+ import { RefreshControl, View, Platform } from 'react-native';
10
9
  import { useIsFocused } from '@react-navigation/native';
11
10
 
12
11
  import { useTranslations } from '../../hooks/Common/useTranslations';
@@ -219,7 +218,7 @@ const UnitDetail = ({ route }) => {
219
218
  isSuccessfullyConnected ? RouterHardware(Routes.Dashboard) : goBack
220
219
  );
221
220
  const user = useSCContextSelector((state) => state?.auth?.account?.user);
222
- const { isLavidaSource, isFirstOpenCamera } = useSCContextSelector(
221
+ const { isLavidaSource, isFirstOpenCamera, appState } = useSCContextSelector(
223
222
  (state) => state.app
224
223
  );
225
224
 
@@ -228,7 +227,6 @@ const UnitDetail = ({ route }) => {
228
227
  const [showAdd, setShowAdd, setHideAdd] = useBoolean();
229
228
  const [showPreventAccess, setShowPreventAccess, setHidePreventAccess] =
230
229
  useBoolean(false);
231
- const appState = useRef(AppState.currentState);
232
230
 
233
231
  const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
234
232
  usePopover();
@@ -306,21 +304,8 @@ const UnitDetail = ({ route }) => {
306
304
  );
307
305
 
308
306
  useEffect(() => {
309
- const handler = (nextAppState) => {
310
- if (
311
- appState.current.match(/inactive|background/) &&
312
- nextAppState === 'active'
313
- ) {
314
- fetchDetails();
315
- }
316
- appState.current = nextAppState;
317
- };
318
- const subscription = AppState.addEventListener('change', handler);
319
-
320
- return () => {
321
- subscription?.remove();
322
- };
323
- }, [fetchDetails]);
307
+ appState === 'active' && fetchDetails();
308
+ }, [appState, fetchDetails]);
324
309
 
325
310
  useValueEvaluations(unitId);
326
311
 
@@ -1,5 +1,5 @@
1
- import React, { memo, useCallback, useEffect, useState, useRef } from 'react';
2
- import { AppState, ScrollView } from 'react-native';
1
+ import React, { memo, useCallback, useEffect, useState } from 'react';
2
+ import { ScrollView } from 'react-native';
3
3
  import SummaryItem from '../../commons/SummaryItem';
4
4
  import Routes from '../../utils/Route';
5
5
  import { useIsFocused, useNavigation } from '@react-navigation/native';
@@ -7,6 +7,7 @@ import { axiosGet } from '../../utils/Apis/axios';
7
7
  import { API } from '../../configs';
8
8
  import { useReceiveNotifications } from '../../hooks';
9
9
  import { AccessibilityLabel } from '../../configs/Constants';
10
+ import { useSCContextSelector } from '../../context';
10
11
 
11
12
  let timeoutId;
12
13
 
@@ -14,7 +15,7 @@ const Summaries = memo(({ unit }) => {
14
15
  const [unitSummaries, setUnitSummaries] = useState([]);
15
16
  const isFocused = useIsFocused();
16
17
  const navigation = useNavigation();
17
- const appState = useRef(AppState.currentState);
18
+ const appState = useSCContextSelector((state) => state.app.appState);
18
19
 
19
20
  const fetchUnitSummary = useCallback(async () => {
20
21
  if (!unit.id) {
@@ -59,20 +60,8 @@ const Summaries = memo(({ unit }) => {
59
60
  }, [fetchUnitSummary]);
60
61
 
61
62
  useEffect(() => {
62
- const subscription = AppState.addEventListener('change', (nextAppState) => {
63
- if (
64
- appState.current.match(/inactive|background/) &&
65
- nextAppState === 'active'
66
- ) {
67
- fetchUnitSummary();
68
- }
69
- appState.current = nextAppState;
70
- });
71
-
72
- return () => {
73
- subscription?.remove();
74
- };
75
- }, [fetchUnitSummary]);
63
+ appState === 'active' && fetchUnitSummary();
64
+ }, [appState, fetchUnitSummary]);
76
65
 
77
66
  useEffect(() => {
78
67
  if (!isFocused) {
@@ -5,13 +5,15 @@ import MenuActionAddnew from '../../../commons/MenuActionAddnew';
5
5
  import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
7
  import AddMenu from '../AddMenu';
8
+ import { useNavigation } from '@react-navigation/native';
9
+ import { ToastBottomHelper } from '../../../utils/Utils';
10
+ import { getTranslate } from '../../../utils/I18n';
8
11
 
9
- const mockedNavigate = jest.fn();
10
12
  const mockedAfterItemClick = jest.fn();
11
13
  const mockedHideAddModal = jest.fn();
12
14
 
13
- const wrapComponent = (unit) => (
14
- <SCProvider initState={mockSCStore({})}>
15
+ const wrapComponent = (unit, storeData = {}) => (
16
+ <SCProvider initState={mockSCStore(storeData)}>
15
17
  <AddMenu
16
18
  unit={unit}
17
19
  afterItemClick={mockedAfterItemClick}
@@ -21,17 +23,15 @@ const wrapComponent = (unit) => (
21
23
  </SCProvider>
22
24
  );
23
25
 
24
- jest.mock('@react-navigation/native', () => {
25
- return {
26
- ...jest.requireActual('@react-navigation/native'),
27
- useNavigation: () => ({
28
- navigate: mockedNavigate,
29
- }),
30
- useIsFocused: jest.fn(),
31
- };
32
- });
33
-
34
26
  describe('Test AddMenu Unit', () => {
27
+ const mockedNavigate = useNavigation().navigate;
28
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
29
+
30
+ beforeEach(() => {
31
+ mockedNavigate.mockClear();
32
+ spyToastError.mockClear();
33
+ });
34
+
35
35
  let tree;
36
36
  it('render AddMenu without route', async () => {
37
37
  let unit = { id: 1, name: 'Unit 1' };
@@ -68,8 +68,9 @@ describe('Test AddMenu Unit', () => {
68
68
  const menuActionAddnew = instance.findByType(MenuActionAddnew);
69
69
  await act(async () => {
70
70
  menuActionAddnew.props.onItemClick({
71
- route: 'route test',
72
- data: 'data test',
71
+ onClick: () => {
72
+ mockedNavigate('route test', 'data test');
73
+ },
73
74
  });
74
75
  });
75
76
 
@@ -77,4 +78,56 @@ describe('Test AddMenu Unit', () => {
77
78
  expect(mockedAfterItemClick).toHaveBeenCalled();
78
79
  expect(mockedNavigate).toHaveBeenCalledWith('route test', 'data test');
79
80
  });
81
+
82
+ it('add new device but have no permission', async () => {
83
+ let unit = { id: 1, name: 'Unit 1' };
84
+ await act(async () => {
85
+ tree = await create(
86
+ wrapComponent(unit, {
87
+ auth: {
88
+ account: {
89
+ user: {
90
+ permissions: {},
91
+ },
92
+ },
93
+ },
94
+ })
95
+ );
96
+ });
97
+
98
+ const instance = tree.root;
99
+ const menuActionAddnew = instance.findByType(MenuActionAddnew);
100
+ await act(async () => {
101
+ menuActionAddnew.props.onItemClick(menuActionAddnew.props.dataActions[2]);
102
+ });
103
+ expect(spyToastError).toBeCalledWith(
104
+ getTranslate('en', 'not_support_plug_and_play')
105
+ );
106
+ });
107
+
108
+ it('add new device has permission', async () => {
109
+ let unit = { id: 1, name: 'Unit 1' };
110
+ await act(async () => {
111
+ tree = await create(
112
+ wrapComponent(unit, {
113
+ auth: {
114
+ account: {
115
+ user: {
116
+ permissions: {
117
+ plug_and_play_zigbee: true,
118
+ },
119
+ },
120
+ },
121
+ },
122
+ })
123
+ );
124
+ });
125
+
126
+ const instance = tree.root;
127
+ const menuActionAddnew = instance.findByType(MenuActionAddnew);
128
+ await act(async () => {
129
+ menuActionAddnew.props.onItemClick(menuActionAddnew.props.dataActions[2]);
130
+ });
131
+ expect(spyToastError).not.toBeCalled();
132
+ });
80
133
  });
@@ -104,10 +104,10 @@ describe('Test Summaries', () => {
104
104
  await act(async () => {
105
105
  tree = await renderer.create(wrapComponent(props));
106
106
  });
107
- expect(mock.history.get).toHaveLength(1);
107
+ expect(mock.history.get).toHaveLength(2);
108
108
  await act(async () => {
109
109
  jest.runOnlyPendingTimers();
110
110
  });
111
- expect(mock.history.get).toHaveLength(2);
111
+ expect(mock.history.get).toHaveLength(3);
112
112
  });
113
113
  });
@@ -59,7 +59,7 @@ const AutomateScript = ({ automate, onPress, isSelected }) => {
59
59
  </Text>
60
60
  <View style={styles.descriptionContainer}>
61
61
  <Text numberOfLines={1} type={'Label'} color={Colors.Gray7}>
62
- {`${t('create_by')} ${author}`}
62
+ {t('created_by', { name: author })}
63
63
  </Text>
64
64
  <IconOutline name="right" size={12} />
65
65
  </View>