@eohjsc/react-native-smart-city 0.7.21 → 0.7.23

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 (101) hide show
  1. package/package.json +1 -1
  2. package/src/Images/Common/default_end_device.png +0 -0
  3. package/src/commons/ActionGroup/TerminalBoxTemplate.js +3 -0
  4. package/src/commons/ActionTemplate/OnOffButtonAction.js +38 -4
  5. package/src/commons/ActionTemplate/OnOffSimpleAction.js +55 -15
  6. package/src/commons/ActionTemplate/OnOffSmartLockAction.js +46 -8
  7. package/src/commons/ActionTemplate/SwitchButtonAction.js +35 -4
  8. package/src/commons/ActionTemplate/ThreeButtonAction.js +13 -3
  9. package/src/commons/ActionTemplate/__test__/OnOffButtonAction.test.js +46 -7
  10. package/src/commons/ActionTemplate/__test__/OnOffSimpleAction.test.js +66 -6
  11. package/src/commons/ActionTemplate/__test__/OnOffSmartLockAction.test.js +53 -13
  12. package/src/commons/ActionTemplate/__test__/SwitchButtonAction.test.js +46 -7
  13. package/src/commons/ActionTemplate/__test__/index.test.js +6 -2
  14. package/src/commons/ActionTemplate/index.js +65 -10
  15. package/src/commons/Dashboard/MyUnit/index.js +19 -20
  16. package/src/commons/DevMode/Search.js +1 -1
  17. package/src/commons/Device/RainningSensor/CurrentRainSensor.js +5 -5
  18. package/src/commons/MediaPlayerDetail/MediaPlayerFull.js +26 -32
  19. package/src/commons/OneTapTemplate/StatesGridActionTemplate.js +8 -6
  20. package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +6 -0
  21. package/src/commons/SubUnit/OneTap/index.js +5 -0
  22. package/src/commons/UnitSummary/ConfigHistoryChart/index.js +9 -11
  23. package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfig.js +2 -2
  24. package/src/commons/Widgets/IFrameWithConfig/__tests__/IFrameWithConfig.test.js +1 -1
  25. package/src/configs/API.js +10 -0
  26. package/src/configs/AccessibilityLabel.js +5 -1
  27. package/src/configs/Images.js +1 -0
  28. package/src/navigations/AddMemberStack.js +3 -3
  29. package/src/screens/ActivityLog/__test__/index.test.js +10 -0
  30. package/src/screens/ActivityLog/hooks/index.js +1 -1
  31. package/src/screens/AddCommon/SelectUnit.js +3 -2
  32. package/src/screens/AddLocationMaps/__test__/index.test.js +13 -13
  33. package/src/screens/Automate/AddNewAction/ChooseAction.js +15 -51
  34. package/src/screens/Automate/AddNewAction/SelectControlDevices.js +13 -3
  35. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +74 -54
  36. package/src/screens/Automate/AddNewAction/__test__/ChooseAction.test.js +114 -4
  37. package/src/screens/Automate/AddNewAction/__test__/ChooseConfig.test.js +9 -11
  38. package/src/screens/Automate/AddNewAction/__test__/SetupConfigCondition.test.js +37 -8
  39. package/src/screens/Automate/AddNewAutoSmart/AddTypeSmart.js +5 -0
  40. package/src/screens/Automate/AddNewAutoSmart/__test__/AddAutomationTypeSmart.test.js +31 -0
  41. package/src/screens/Automate/AddNewAutoSmart/__test__/AddNewAutoSmart.test.js +18 -2
  42. package/src/screens/Automate/Components/InputName.js +7 -6
  43. package/src/screens/Automate/Constants.js +12 -0
  44. package/src/screens/Automate/EditActionsList/UpdateActionScript.js +24 -55
  45. package/src/screens/Automate/EditActionsList/__tests__/UpdateActionScript.test.js +298 -41
  46. package/src/screens/Automate/EditActionsList/__tests__/index.test.js +2 -2
  47. package/src/screens/Automate/EditActionsList/index.js +26 -14
  48. package/src/screens/Automate/MultiUnits.js +9 -1
  49. package/src/screens/Automate/OneTap/__test__/AddNewOneTap.test.js +3 -3
  50. package/src/screens/Automate/ScriptDetail/Components/AddActionScript.js +4 -10
  51. package/src/screens/Automate/ScriptDetail/Components/DeleteScript.js +2 -4
  52. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +78 -0
  53. package/src/screens/Automate/ScriptDetail/index.js +16 -10
  54. package/src/screens/Automate/ScriptDetail/utils.js +39 -35
  55. package/src/screens/Automate/SetSchedule/AddEditConditionSchedule.js +27 -160
  56. package/src/screens/Automate/SetSchedule/EditSchedule.js +269 -0
  57. package/src/screens/Automate/SetSchedule/__test__/AddEditConditionSchedule.test.js +327 -22
  58. package/src/screens/Automate/SetSchedule/__test__/index.test.js +35 -22
  59. package/src/screens/Automate/SetSchedule/components/RepeatOptionsPopup.js +2 -8
  60. package/src/screens/Automate/SetSchedule/index.js +15 -129
  61. package/src/screens/Automate/SetSchedule/styles/indexStyles.js +9 -0
  62. package/src/screens/Automate/__test__/MultiUnits.test.js +6 -1
  63. package/src/screens/Automate/hooks/useAction.js +222 -0
  64. package/src/screens/ConfirmUnitDeletion/__test__/ConfirmUnitDeletion.test.js +69 -13
  65. package/src/screens/ConfirmUnitDeletion/index.js +14 -14
  66. package/src/screens/Device/__test__/detail.test.js +48 -1
  67. package/src/screens/Device/detail.js +46 -3
  68. package/src/screens/PlayBackCamera/__test__/index.test.js +48 -13
  69. package/src/screens/PlayBackCamera/index.js +1 -1
  70. package/src/screens/Sharing/Components/ConfigItem.js +34 -0
  71. package/src/screens/Sharing/Components/DeviceItem.js +77 -0
  72. package/src/screens/Sharing/Components/ItemChangeRole.js +3 -4
  73. package/src/screens/Sharing/Components/ShareDeviceSelector.js +255 -0
  74. package/src/screens/Sharing/Components/Styles/CheckBoxCustomStyles.js +1 -1
  75. package/src/screens/Sharing/Components/Styles/DeviceItemStyles.js +11 -27
  76. package/src/screens/Sharing/{Styles/SelectPermissionStyles.js → Components/Styles/ShareDeviceSelectorStyles.js} +3 -11
  77. package/src/screens/Sharing/Components/SubUnitItem.js +28 -0
  78. package/src/screens/Sharing/Components/SubUnitTreeView.js +68 -0
  79. package/src/screens/Sharing/Components/TitleCheckBox.js +23 -41
  80. package/src/screens/Sharing/Components/__test__/ItemChangeRole.test.js +7 -7
  81. package/src/screens/Sharing/Components/__test__/ShareDeviceSelector.test.js +298 -0
  82. package/src/screens/Sharing/Components/index.js +14 -1
  83. package/src/screens/Sharing/InfoMemberUnit.js +20 -20
  84. package/src/screens/Sharing/SelectShareDevice.js +11 -255
  85. package/src/screens/Sharing/SelectUser.js +12 -12
  86. package/src/screens/Sharing/UpdateShareDevice.js +45 -301
  87. package/src/screens/Sharing/__test__/InfoMemberUnit.test.js +58 -11
  88. package/src/screens/Sharing/__test__/SelectShareDevice.test.js +51 -160
  89. package/src/screens/Sharing/__test__/SelectUser.test.js +72 -10
  90. package/src/screens/Sharing/__test__/UpdateShareDevice.test.js +49 -209
  91. package/src/utils/Apis/axios.js +6 -0
  92. package/src/utils/I18n/translations/en.js +9 -1
  93. package/src/utils/I18n/translations/vi.js +10 -2
  94. package/src/commons/Sharing/StationDevicePermissions.js +0 -204
  95. package/src/screens/Automate/constants.js +0 -0
  96. package/src/screens/Sharing/Components/CheckBoxConfig.js +0 -44
  97. package/src/screens/Sharing/Components/CheckBoxSubUnit.js +0 -35
  98. package/src/screens/Sharing/Components/EndDevice.js +0 -93
  99. package/src/screens/Sharing/Components/Styles/CheckBoxConfigStyles.js +0 -18
  100. package/src/screens/Sharing/Components/Styles/TitleCheckBoxStyles.js +0 -21
  101. package/src/screens/Sharing/Components/__test__/TitleCheckBox.test.js +0 -31
@@ -54,7 +54,7 @@ import Routes from '../../utils/Route';
54
54
  import { getData as getLocalData } from '../../utils/Storage';
55
55
  import { API, Colors, SCConfig } from '../../configs';
56
56
  import { DEVICE_TYPE, AccessibilityLabel } from '../../configs/Constants';
57
- import { axiosGet } from '../../utils/Apis/axios';
57
+ import { axiosGet, axiosPost } from '../../utils/Apis/axios';
58
58
  import { notImplemented } from '../../utils/Utils';
59
59
  import { useReceiveNotifications } from '../../hooks';
60
60
  import useChipJsonConfiguration, {
@@ -482,6 +482,31 @@ const DeviceDetail = ({ route }) => {
482
482
  fetchDataDeviceDetail();
483
483
  }, [device, isNetworkConnected, fetchDataDeviceDetail]);
484
484
 
485
+ const fetchDeviceStatus = useCallback(async () => {
486
+ if (
487
+ !isNetworkConnected ||
488
+ !device.is_managed_by_backend ||
489
+ configIdsTemp.current.length
490
+ ) {
491
+ return;
492
+ }
493
+
494
+ const { success, data } = await axiosPost(
495
+ API.UNIT.END_DEVICES_STATUS(unit.id),
496
+ {
497
+ end_device_ids: [device.id],
498
+ }
499
+ );
500
+
501
+ success && setAction(Action.SET_DEVICES_STATUS, data);
502
+ }, [
503
+ isNetworkConnected,
504
+ unit.id,
505
+ device.id,
506
+ device.is_managed_by_backend,
507
+ setAction,
508
+ ]);
509
+
485
510
  const onRefresh = useCallback(async () => {
486
511
  await fetchSensorDetail();
487
512
  await fetchDataDeviceDetail();
@@ -495,9 +520,11 @@ const DeviceDetail = ({ route }) => {
495
520
  })();
496
521
  }
497
522
  await checkScanDevicesBLE();
523
+ await fetchDeviceStatus();
498
524
  }, [
499
525
  fetchSensorDetail,
500
526
  fetchDataDeviceDetail,
527
+ fetchDeviceStatus,
501
528
  unit?.remote_control_options,
502
529
  isNetworkConnected,
503
530
  checkScanDevicesBLE,
@@ -516,11 +543,20 @@ const DeviceDetail = ({ route }) => {
516
543
  return;
517
544
  }
518
545
 
519
- const { configs, options, config, from_config, to_config } =
520
- configuration;
546
+ const {
547
+ configs,
548
+ options,
549
+ config,
550
+ from_config,
551
+ to_config,
552
+ history_configs,
553
+ realtime_configs,
554
+ } = configuration;
521
555
 
522
556
  configs?.forEach((object) => configIdsSet.add(object.id));
523
557
  options?.forEach((option) => configIdsSet.add(option.config));
558
+ history_configs?.forEach((object) => configIdsSet.add(object.id));
559
+ realtime_configs?.forEach((object) => configIdsSet.add(object.id));
524
560
 
525
561
  if (config) {
526
562
  const configId = config.id || config;
@@ -622,6 +658,13 @@ const DeviceDetail = ({ route }) => {
622
658
  }, [display.items, isNetworkConnected, device, mqttConfigs])
623
659
  );
624
660
 
661
+ useFocusEffect(
662
+ useCallback(() => {
663
+ fetchDeviceStatus();
664
+ // eslint-disable-next-line react-hooks/exhaustive-deps
665
+ }, [])
666
+ );
667
+
625
668
  useWatchConfigs(chipFiltered.length ? [] : configIdsTemp.current);
626
669
 
627
670
  const isShowEmergencyResolve =
@@ -20,19 +20,22 @@ const wrapComponent = () => (
20
20
 
21
21
  describe('Test PlayBackCamera', () => {
22
22
  let tree;
23
- jest.useFakeTimers();
24
- global.mockedUseRoute.mockReturnValue({
25
- params: {
26
- item: {
27
- configuration: {
28
- time_zone: 0,
29
- playback: '',
30
- name: 'name',
31
- uri: '',
23
+
24
+ beforeEach(() => {
25
+ jest.useFakeTimers();
26
+ global.mockedUseRoute.mockReturnValue({
27
+ params: {
28
+ item: {
29
+ configuration: {
30
+ time_zone: 0,
31
+ playback: 'rtsp://example.com/playback',
32
+ name: 'name',
33
+ uri: 'rtsp://example.com/stream',
34
+ },
32
35
  },
36
+ thumbnail: {},
33
37
  },
34
- thumbnail: {},
35
- },
38
+ });
36
39
  });
37
40
 
38
41
  it('Test render', async () => {
@@ -129,7 +132,9 @@ describe('Test PlayBackCamera', () => {
129
132
  },
130
133
  });
131
134
  });
132
- jest.runAllTimers();
135
+ await act(async () => {
136
+ jest.runAllTimers();
137
+ });
133
138
  //NOTE: isFirstTime = false
134
139
  await act(async () => {
135
140
  timerScroll[0].props.onScroll({
@@ -147,7 +152,37 @@ describe('Test PlayBackCamera', () => {
147
152
  });
148
153
  const media = instance.findByType(MediaPlayerFull);
149
154
  expect(media.props.uri).toEqual(
150
- `=${moment().format('YYYYMMDDT003730\\Z')}`
155
+ `rtsp://example.com/playback=${moment().format('YYYYMMDDT003730\\Z')}`
156
+ );
157
+ });
158
+
159
+ it('Test render when playback is null', async () => {
160
+ global.mockedUseRoute.mockReturnValue({
161
+ params: {
162
+ item: {
163
+ configuration: {
164
+ time_zone: 0,
165
+ playback: null,
166
+ name: 'name',
167
+ uri: 'rtsp://example.com/stream',
168
+ },
169
+ },
170
+ thumbnail: {},
171
+ },
172
+ });
173
+ await act(async () => {
174
+ tree = await create(wrapComponent());
175
+ });
176
+ const instance = tree.root;
177
+ const buttonAddDate = instance.findAll(
178
+ (el) =>
179
+ el.props.accessibilityLabel === AccessibilityLabel.ON_PRESS_ADD_DATE &&
180
+ el.type === TouchableOpacity
151
181
  );
182
+ await act(async () => {
183
+ await buttonAddDate[0].props.onPress();
184
+ });
185
+ const media = instance.findByType(MediaPlayerFull);
186
+ expect(media.props.uri).toEqual('rtsp://example.com/stream');
152
187
  });
153
188
  });
@@ -121,7 +121,7 @@ const PlayBackCamera = () => {
121
121
  'YYYY-MM-DD HH:mm:SS'
122
122
  ).valueOf();
123
123
 
124
- if (selectedDayTime > currentTime) {
124
+ if (selectedDayTime > currentTime || !playback) {
125
125
  setUriCamera(uri);
126
126
  } else {
127
127
  if (playback.includes('chID')) {
@@ -0,0 +1,34 @@
1
+ import React from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import { Text } from '../../../commons';
4
+ import { TitleCheckBox } from '.';
5
+ import { View } from 'react-native';
6
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
7
+
8
+ const ConfigItem = ({ isChecked, item, onSelect }) => {
9
+ const t = useTranslations();
10
+ return (
11
+ <View style={styles.wrap}>
12
+ <TitleCheckBox
13
+ isChecked={isChecked}
14
+ item={item}
15
+ onSelect={onSelect}
16
+ checkBoxStyle={styles.wrapCheckBoxStyle}
17
+ />
18
+ <Text>{item?.isConfig ? t('view_only') : t('can_control')}</Text>
19
+ </View>
20
+ );
21
+ };
22
+
23
+ export default ConfigItem;
24
+
25
+ const styles = StyleSheet.create({
26
+ wrap: {
27
+ flexDirection: 'row',
28
+ alignItems: 'center',
29
+ justifyContent: 'space-between',
30
+ },
31
+ wrapCheckBoxStyle: {
32
+ marginLeft: -10,
33
+ },
34
+ });
@@ -0,0 +1,77 @@
1
+ import { Colors, Images } from '../../../configs';
2
+ import React, { useState } from 'react';
3
+ import { Text, TouchableOpacity, View } from 'react-native';
4
+
5
+ import { AccessibilityLabel } from '../../../configs/Constants';
6
+ import FImage from '../../../commons/FImage';
7
+ import IconComponent from '../../../commons/IconComponent';
8
+ import { IconOutline } from '@ant-design/icons-react-native';
9
+ import styles from './Styles/DeviceItemStyles';
10
+
11
+ const DeviceItem = ({ isChecked, item, onSelect, childrenRender }) => {
12
+ const { label, key, item: endDeviceItem } = item;
13
+ const { actions, read_configs, icon_kit, icon } = endDeviceItem;
14
+ const [isChildExpanded, setIsChildExpanded] = useState(false);
15
+ const onPressItem = () => {
16
+ onSelect(item, !isChecked);
17
+ };
18
+
19
+ return (
20
+ <View style={styles.wrap}>
21
+ <TouchableOpacity
22
+ onPress={onPressItem}
23
+ accessibilityLabel={`${AccessibilityLabel.SHARE_DEVICE.ICON_END_DEVICE}-${key}`}
24
+ >
25
+ {icon_kit || icon ? (
26
+ <IconComponent
27
+ icon={icon_kit || icon}
28
+ size={20}
29
+ style={styles.viewLeft}
30
+ />
31
+ ) : (
32
+ <FImage
33
+ source={Images.defaultEndDeviceIcon}
34
+ style={styles.viewLeft}
35
+ />
36
+ )}
37
+ </TouchableOpacity>
38
+ <View style={styles.wrapRight}>
39
+ <View style={styles.viewRight}>
40
+ <TouchableOpacity
41
+ onPress={onPressItem}
42
+ style={styles.wrapTextIcon}
43
+ accessibilityLabel={`${AccessibilityLabel.SHARE_DEVICE.NAME_END_DEVICE}-${key}`}
44
+ >
45
+ <Text numberOfLines={1} style={styles.text}>
46
+ {label}
47
+ </Text>
48
+ {isChecked && (
49
+ <IconOutline
50
+ name={'check'}
51
+ color={Colors.Primary}
52
+ size={20}
53
+ accessibilityLabel={`${AccessibilityLabel.SHARE_DEVICE.ICON_CHECK}-${key}`}
54
+ />
55
+ )}
56
+ </TouchableOpacity>
57
+ <View style={styles.expandView}>
58
+ {actions?.length + read_configs?.length > 0 && (
59
+ <IconOutline
60
+ onPress={() => setIsChildExpanded(!isChildExpanded)}
61
+ name={isChildExpanded ? 'up' : 'down'}
62
+ size={20}
63
+ color={Colors.Gray6}
64
+ accessibilityLabel={`${AccessibilityLabel.SHARE_DEVICE.EXPAND_END_DEVICE}-${key}`}
65
+ />
66
+ )}
67
+ </View>
68
+ </View>
69
+ <View style={styles.wrapChildren}>
70
+ {isChildExpanded && childrenRender}
71
+ </View>
72
+ </View>
73
+ </View>
74
+ );
75
+ };
76
+
77
+ export default DeviceItem;
@@ -1,16 +1,15 @@
1
1
  import React, { memo } from 'react';
2
2
  import { TouchableOpacity, View } from 'react-native';
3
- import Text from '../../../commons/Text';
4
- import { Colors } from '../../../configs';
5
3
 
6
- import styles from './Styles/ItemChangeRoleStyles';
4
+ import { Colors } from '../../../configs';
7
5
  import { RadioCircle } from '../../../commons';
6
+ import Text from '../../../commons/Text';
7
+ import styles from './Styles/ItemChangeRoleStyles';
8
8
 
9
9
  const ItemChangeRole = ({
10
10
  isChecked,
11
11
  onPress,
12
12
  wrapStyle,
13
- wrapCheckBoxStyle,
14
13
  icon,
15
14
  title = '',
16
15
  message = '',
@@ -0,0 +1,255 @@
1
+ import { ActivityIndicator, Alert, View } from 'react-native';
2
+ import { ConfigItem, DeviceItem, SubUnitItem, SubUnitTreeView } from '.';
3
+ import React, { useEffect, useMemo, useState } from 'react';
4
+
5
+ import { API } from '../../../configs';
6
+ import { AccessibilityLabel } from '../../../configs/Constants';
7
+ import Text from '../../../commons/Text';
8
+ import ViewButtonBottom from '../../../commons/ViewButtonBottom';
9
+ import { axiosGet } from '../../../utils/Apis/axios';
10
+ import styles from './Styles/ShareDeviceSelectorStyles';
11
+ import { useNavigation } from '@react-navigation/native';
12
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
13
+
14
+ const ShareDeviceSelector = ({
15
+ unitId,
16
+ initialSelectedKeys = [],
17
+ onRightClick,
18
+ rightTitle,
19
+ }) => {
20
+ const [dataSubUnits, setDataSubUnits] = useState([]);
21
+ const [loading, setLoading] = useState(true);
22
+ const t = useTranslations();
23
+ const navigation = useNavigation();
24
+ const [selectedKeys, setSelectedKeys] = useState(initialSelectedKeys);
25
+
26
+ const updateParentSelected = (data, newSelectedKeys) => {
27
+ const updateParentCheck = (nodes) => {
28
+ nodes.forEach((node) => {
29
+ if (!node?.children || node.children.length === 0) {
30
+ return;
31
+ }
32
+ updateParentCheck(node.children);
33
+
34
+ const allChildrenChecked = node.children.every((child) =>
35
+ newSelectedKeys.has(child.key)
36
+ );
37
+ if (allChildrenChecked) {
38
+ newSelectedKeys.add(node.key);
39
+ } else {
40
+ newSelectedKeys.delete(node.key);
41
+ }
42
+ });
43
+ };
44
+ updateParentCheck(data);
45
+ setSelectedKeys([...newSelectedKeys]);
46
+ };
47
+
48
+ const handleOnSelect = (item, isChecked) => {
49
+ const getAllChildIds = (node) => {
50
+ let ids = [node.key];
51
+ (node?.children || []).forEach((child) => {
52
+ ids.push(...getAllChildIds(child));
53
+ });
54
+ return ids;
55
+ };
56
+
57
+ let newSelectedKeys = new Set(selectedKeys);
58
+ const affectedKeys = getAllChildIds(item);
59
+
60
+ if (isChecked) {
61
+ affectedKeys.forEach((key) => newSelectedKeys.add(key));
62
+ } else {
63
+ affectedKeys.forEach((key) => newSelectedKeys.delete(key));
64
+ }
65
+
66
+ updateParentSelected(dataSource, newSelectedKeys);
67
+ };
68
+
69
+ const renderSubUnitTreeNode = ({ item, childrenRender }) => {
70
+ const level = item.key.split('-').at(0);
71
+ const isChecked = selectedKeys.includes(item.key);
72
+ switch (level) {
73
+ case 'subUnit':
74
+ return (
75
+ <SubUnitItem
76
+ item={item}
77
+ isChecked={isChecked}
78
+ onSelect={handleOnSelect}
79
+ childrenRender={childrenRender}
80
+ />
81
+ );
82
+ case 'device':
83
+ return (
84
+ <DeviceItem
85
+ item={item}
86
+ isChecked={isChecked}
87
+ childrenRender={childrenRender}
88
+ onSelect={handleOnSelect}
89
+ />
90
+ );
91
+ case 'config':
92
+ case 'action':
93
+ return (
94
+ <ConfigItem
95
+ isChecked={isChecked}
96
+ item={item}
97
+ onSelect={handleOnSelect}
98
+ />
99
+ );
100
+ }
101
+ };
102
+
103
+ const [dataSource, keyDeviceMap] = useMemo(() => {
104
+ let keyMap = {};
105
+ const items = [
106
+ {
107
+ id: 'all',
108
+ key: 'all',
109
+ label: t('text_all_devices'),
110
+ children: dataSubUnits.map((subUnit) => ({
111
+ id: subUnit.id,
112
+ key: `subUnit-${subUnit.id}`,
113
+ label: subUnit.name,
114
+ children: subUnit.devices.map((device) => ({
115
+ id: device.id,
116
+ key: `device-${device.id}`,
117
+ label: device.name,
118
+ item: device,
119
+ children: [
120
+ ...device.actions.map((action) => {
121
+ const key = `action-${action.id}`;
122
+ keyMap[key] = device.id;
123
+ return {
124
+ id: action.id,
125
+ key,
126
+ label: action.name,
127
+ isConfig: false,
128
+ };
129
+ }),
130
+ ...device.read_configs.map((config) => {
131
+ const key = `config-${config.id}`;
132
+ keyMap[key] = device.id;
133
+ return {
134
+ id: config.id,
135
+ key,
136
+ label: config.name,
137
+ isConfig: true,
138
+ };
139
+ }),
140
+ ],
141
+ })),
142
+ })),
143
+ },
144
+ ];
145
+ return [items, keyMap];
146
+ }, [dataSubUnits, t]);
147
+
148
+ useEffect(() => {
149
+ if (!initialSelectedKeys.length) {
150
+ return;
151
+ }
152
+ const newSelectedKeys = new Set(initialSelectedKeys);
153
+ updateParentSelected(dataSource, newSelectedKeys);
154
+ }, [initialSelectedKeys, dataSource]);
155
+
156
+ useEffect(() => {
157
+ const getUnitPermission = async () => {
158
+ setLoading(true);
159
+ const { success, data } = await axiosGet(
160
+ API.SHARE.UNIT_PERMISSIONS_v2(unitId)
161
+ );
162
+ success && setDataSubUnits(data);
163
+ setLoading(false);
164
+ };
165
+
166
+ getUnitPermission();
167
+ }, [unitId]);
168
+
169
+ const handleOnRightClick = async () => {
170
+ setLoading(true);
171
+
172
+ const readKeys = selectedKeys.filter((key) => key.startsWith('config'));
173
+ const controlKeys = selectedKeys.filter((key) => key.startsWith('action'));
174
+ const endDeviceKeys = selectedKeys.filter((key) =>
175
+ key.startsWith('device')
176
+ );
177
+
178
+ const groupByDevice = (keys) =>
179
+ keys.reduce((acc, key) => {
180
+ const [, id] = key.split('-');
181
+ const deviceId = keyDeviceMap[key];
182
+ deviceId && (acc[deviceId] ??= []).push(Number(id));
183
+ return acc;
184
+ }, {});
185
+ const readGroup = groupByDevice(readKeys);
186
+ const controlGroup = groupByDevice(controlKeys);
187
+
188
+ const read_permissions = Object.entries(readGroup).map(
189
+ ([deviceId, configIds]) => ({
190
+ id: Number(deviceId),
191
+ values: configIds,
192
+ })
193
+ );
194
+
195
+ const control_permissions = Object.entries(controlGroup).map(
196
+ ([deviceId, actionIds]) => ({
197
+ id: Number(deviceId),
198
+ values: actionIds,
199
+ })
200
+ );
201
+
202
+ endDeviceKeys.forEach((key) => {
203
+ const [, deviceId] = key.split('-');
204
+ if (!readGroup[deviceId] && !controlGroup[deviceId]) {
205
+ read_permissions.push({ id: Number(deviceId), values: [] });
206
+ }
207
+ });
208
+
209
+ if (!read_permissions.length && !control_permissions.length) {
210
+ setLoading(false);
211
+ Alert.alert('', t('choose_at_least_one'));
212
+ return;
213
+ }
214
+
215
+ read_permissions.sort((a, b) => a.id - b.id);
216
+ control_permissions.sort((a, b) => a.id - b.id);
217
+
218
+ await onRightClick?.({ read_permissions, control_permissions });
219
+ setLoading(false);
220
+ };
221
+ return (
222
+ <View style={styles.wrap}>
223
+ <Text semibold style={styles.title}>
224
+ {t('select_device')}
225
+ </Text>
226
+ <Text style={styles.subtitle}>{t('sharing_select_devices_hint')}</Text>
227
+ <View style={styles.contentContainer}>
228
+ {loading ? (
229
+ <ActivityIndicator />
230
+ ) : (
231
+ <SubUnitTreeView
232
+ data={dataSource}
233
+ render={renderSubUnitTreeNode}
234
+ onSelect={handleOnSelect}
235
+ selectedKeys={selectedKeys}
236
+ />
237
+ )}
238
+ </View>
239
+ <View style={styles.wrapViewButtonStyle}>
240
+ <ViewButtonBottom
241
+ accessibilityLabelPrefix={
242
+ AccessibilityLabel.PREFIX.SHARING_SELECT_PERMISSION
243
+ }
244
+ leftTitle={t('cancel')}
245
+ onLeftClick={() => navigation.goBack()}
246
+ rightTitle={rightTitle}
247
+ rightDisabled={false}
248
+ onRightClick={handleOnRightClick}
249
+ />
250
+ </View>
251
+ </View>
252
+ );
253
+ };
254
+
255
+ export default ShareDeviceSelector;
@@ -1,5 +1,5 @@
1
- import { StyleSheet } from 'react-native';
2
1
  import { Colors } from '../../../../configs';
2
+ import { StyleSheet } from 'react-native';
3
3
  import { normalize } from '../../../../configs/Constants';
4
4
 
5
5
  export default StyleSheet.create({
@@ -1,27 +1,13 @@
1
- import { StyleSheet } from 'react-native';
2
1
  import { Colors } from '../../../../configs';
2
+ import { StyleSheet } from 'react-native';
3
3
  import { normalize } from '../../../../configs/Constants';
4
4
 
5
5
  export default StyleSheet.create({
6
- wrapCheckBoxStyle: {
7
- marginLeft: normalize(-10),
8
- },
9
- titleStyle: {
10
- fontSize: 16,
11
- fontWeight: 'normal',
12
- width: 200,
13
- },
14
- wrapStyleTitle: {
15
- justifyContent: 'space-between',
16
- },
17
6
  wrap: {
18
7
  flexDirection: 'row',
19
8
  marginHorizontal: normalize(10),
20
9
  paddingBottom: 10,
21
10
  },
22
- isRenderSeparated: {
23
- paddingBottom: normalize(16),
24
- },
25
11
  viewLeft: {
26
12
  marginRight: 16,
27
13
  width: normalize(24),
@@ -32,26 +18,24 @@ export default StyleSheet.create({
32
18
  flex: 1,
33
19
  },
34
20
  viewRight: {
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ marginTop: normalize(16),
24
+ },
25
+ wrapTextIcon: {
35
26
  flexDirection: 'row',
36
27
  alignItems: 'center',
37
28
  justifyContent: 'space-between',
38
- paddingTop: normalize(16),
29
+ flex: 1,
39
30
  },
40
31
  text: {
41
32
  color: Colors.Gray9,
42
33
  flex: 1,
43
34
  },
44
- checkBox: {
45
- width: normalize(20),
46
- height: normalize(20),
35
+ expandView: {
36
+ width: 20,
47
37
  },
48
-
49
- wrapExpand: {
50
- marginTop: 10,
51
- },
52
- viewSeparated: {
53
- height: 1,
54
- backgroundColor: Colors.Gray4,
55
- marginTop: normalize(16),
38
+ wrapChildren: {
39
+ marginTop: normalize(10),
56
40
  },
57
41
  });
@@ -1,7 +1,8 @@
1
+ import { Constants, normalize } from '../../../../configs/Constants';
2
+
3
+ import { Colors } from '../../../../configs';
1
4
  import { StyleSheet } from 'react-native';
2
5
  import { getStatusBarHeight } from 'react-native-iphone-x-helper';
3
- import { Colors } from '../../../configs';
4
- import { Constants, normalize } from '../../../configs/Constants';
5
6
 
6
7
  export default StyleSheet.create({
7
8
  wrap: {
@@ -31,15 +32,6 @@ export default StyleSheet.create({
31
32
  checkBoxTile: {
32
33
  marginLeft: 5,
33
34
  },
34
- wrapDevice: {
35
- borderRadius: normalize(20),
36
- borderWidth: 1,
37
- borderColor: Colors.Gray4,
38
- backgroundColor: Colors.White,
39
- },
40
- viewGroup: {
41
- marginTop: normalize(8),
42
- },
43
35
  wrapAllDevices: {
44
36
  marginBottom: -10,
45
37
  marginLeft: 5,
@@ -0,0 +1,28 @@
1
+ import { Colors } from '../../../configs';
2
+ import React from 'react';
3
+ import { StyleSheet } from 'react-native';
4
+ import { TitleCheckBox } from '.';
5
+ import { View } from 'react-native';
6
+ import { normalize } from '../../../configs/Constants';
7
+
8
+ const SubUnitItem = ({ item, isChecked, onSelect, childrenRender }) => {
9
+ return (
10
+ <View style={styles.viewGroup}>
11
+ <TitleCheckBox isChecked={isChecked} item={item} onSelect={onSelect} />
12
+ <View style={styles.wrapDevice}>{childrenRender}</View>
13
+ </View>
14
+ );
15
+ };
16
+
17
+ export default SubUnitItem;
18
+ const styles = StyleSheet.create({
19
+ wrapDevice: {
20
+ borderRadius: normalize(20),
21
+ borderWidth: 1,
22
+ borderColor: Colors.Gray4,
23
+ backgroundColor: Colors.White,
24
+ },
25
+ viewGroup: {
26
+ marginTop: normalize(8),
27
+ },
28
+ });