@eohjsc/react-native-smart-city 0.4.65 → 0.4.67

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 (26) hide show
  1. package/android/build.gradle +3 -3
  2. package/package.json +2 -2
  3. package/src/commons/Device/ConnectedViewHeader.js +3 -13
  4. package/src/commons/Device/ItemDevice.js +54 -92
  5. package/src/commons/Device/ItemDeviceWrapper.js +109 -0
  6. package/src/commons/Device/LastUpdatedText.js +31 -0
  7. package/src/commons/Device/__test__/ConnectedViewHeader.test.js +10 -4
  8. package/src/commons/SubUnit/DeviceTemplate/ConfigAndEvaluation/ConfigAndEvaluation.js +46 -0
  9. package/src/commons/SubUnit/DeviceTemplate/ConfigAndEvaluation/index.js +12 -0
  10. package/src/commons/SubUnit/DeviceTemplate/ConfigValue/ConfigValue.js +41 -0
  11. package/src/commons/SubUnit/DeviceTemplate/ConfigValue/index.js +12 -0
  12. package/src/commons/SubUnit/DeviceTemplate/DeviceTemplate.js +21 -0
  13. package/src/commons/SubUnit/DeviceTemplate/EvaluationOverConfig/EvaluationOverConfig.js +79 -0
  14. package/src/commons/SubUnit/DeviceTemplate/EvaluationOverConfig/index.js +12 -0
  15. package/src/commons/SubUnit/DeviceTemplate/index.js +9 -0
  16. package/src/commons/SubUnit/DeviceTemplate/register.js +14 -0
  17. package/src/commons/SubUnit/DeviceTemplate/styles/ConfigValueStyles.js +23 -0
  18. package/src/commons/SubUnit/ShortDetail.js +68 -42
  19. package/src/commons/SubUnit/__test__/ShortDetail.test.js +113 -9
  20. package/src/screens/Device/__test__/detail.test.js +2 -6
  21. package/src/screens/Device/components/SensorConnectStatusViewHeader.js +54 -50
  22. package/src/screens/Device/detail.js +3 -1
  23. package/src/screens/Device/hooks/useEvaluateValue.js +5 -0
  24. package/src/screens/Sharing/Components/__test__/DeviceItem.test.js +2 -2
  25. package/src/screens/SmartAccount/ListDevice/__test__/DeviceItem.test.js +1 -1
  26. package/src/utils/Converter/time.js +4 -0
@@ -10,10 +10,10 @@
10
10
  // original location:
11
11
  // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
12
12
 
13
- def DEFAULT_COMPILE_SDK_VERSION = 28
13
+ def DEFAULT_COMPILE_SDK_VERSION = 32
14
14
  def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3'
15
- def DEFAULT_MIN_SDK_VERSION = 16
16
- def DEFAULT_TARGET_SDK_VERSION = 28
15
+ def DEFAULT_MIN_SDK_VERSION = 24
16
+ def DEFAULT_TARGET_SDK_VERSION = 32
17
17
 
18
18
  def safeExtGet(prop, fallback) {
19
19
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
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.4.65",
4
+ "version": "0.4.67",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -186,7 +186,7 @@
186
186
  "react-native-pager-view": "^5.4.1",
187
187
  "react-native-parallax-scroll-view": "^0.21.3",
188
188
  "react-native-parsed-text": "^0.0.22",
189
- "react-native-permissions": "3.4.0",
189
+ "react-native-permissions": "3.6.0",
190
190
  "react-native-popover-view": "^4.0.3",
191
191
  "react-native-progress": "^5.0.0",
192
192
  "react-native-reanimated": "1.10.1",
@@ -4,7 +4,7 @@ import { IconOutline } from '@ant-design/icons-react-native';
4
4
  import { useTranslations } from '../../hooks/Common/useTranslations';
5
5
  import { Colors } from '../../configs';
6
6
  import Text from '../../commons/Text';
7
- import { timeDifference } from '../../utils/Converter/time';
7
+ import LastUpdatedText from './LastUpdatedText';
8
8
 
9
9
  const DisplayTextConnected = memo(({ type }) => {
10
10
  const t = useTranslations();
@@ -26,9 +26,6 @@ const DisplayTextConnected = memo(({ type }) => {
26
26
  const ConnectedViewHeader = memo(
27
27
  ({ lastUpdated, type, isDisplayTime, showWindDirection }) => {
28
28
  const t = useTranslations();
29
- const lastUpdatedStr = lastUpdated
30
- ? timeDifference(new Date(), lastUpdated)
31
- : `5 ${t('seconds_ago')}`;
32
29
 
33
30
  return (
34
31
  <View style={styles.statusContainer}>
@@ -36,12 +33,10 @@ const ConnectedViewHeader = memo(
36
33
  <IconOutline name={'wifi'} color={Colors.Green6} size={16} />
37
34
  <DisplayTextConnected type={type} />
38
35
  </View>
39
- {lastUpdatedStr && isDisplayTime && (
36
+ {isDisplayTime && (
40
37
  <View style={styles.showWindDirection}>
41
38
  {showWindDirection && <Text>{t('current_wind_direction')} </Text>}
42
- <Text color={Colors.Gray7} size={12} style={styles.txtLastUpdate}>
43
- {`${t('last_updated')} ${lastUpdatedStr}`}
44
- </Text>
39
+ <LastUpdatedText lastUpdated={lastUpdated} showText />
45
40
  </View>
46
41
  )}
47
42
  </View>
@@ -66,11 +61,6 @@ const styles = StyleSheet.create({
66
61
  fontSize: 14,
67
62
  color: Colors.Green6,
68
63
  },
69
- txtLastUpdate: {
70
- marginLeft: 8,
71
- lineHeight: 20,
72
- marginTop: 8,
73
- },
74
64
  showWindDirection: {
75
65
  flexDirection: 'column',
76
66
  justifyContent: 'center',
@@ -1,10 +1,5 @@
1
- import React, { memo, useCallback } from 'react';
2
- import {
3
- StyleSheet,
4
- TouchableOpacity,
5
- TouchableWithoutFeedback,
6
- View,
7
- } from 'react-native';
1
+ import React, { memo, useCallback, useMemo } from 'react';
2
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
8
3
  import Routes from '../../utils/Route';
9
4
  import { IconOutline } from '@ant-design/icons-react-native';
10
5
  import { useNavigation } from '@react-navigation/native';
@@ -12,18 +7,15 @@ import { useTranslations } from '../../hooks/Common/useTranslations';
12
7
  import ItemQuickAction from '../../commons/Action/ItemQuickAction';
13
8
  import Text from '../../commons/Text';
14
9
  import {
15
- useHomeAssistantDeviceConnected,
16
10
  useBluetoothDeviceConnected,
17
11
  useEoHBackendDeviceConnected,
12
+ useHomeAssistantDeviceConnected,
18
13
  } from '../../hooks/IoT';
19
14
 
20
15
  import { Colors } from '../../configs';
21
- import {
22
- AccessibilityLabel,
23
- DEVICE_TYPE,
24
- DEVICE_SIZE,
25
- } from '../../configs/Constants';
16
+ import { DEVICE_TYPE } from '../../configs/Constants';
26
17
  import IconComponent from '../IconComponent';
18
+ import ItemDeviceWrapper from './ItemDeviceWrapper';
27
19
 
28
20
  const ItemDevice = memo(
29
21
  ({ svgMain, description, title, sensor, unit, station, wrapStyle }) => {
@@ -52,29 +44,7 @@ const ItemDevice = memo(
52
44
  });
53
45
  }, [navigation, sensor, station, title, unit]);
54
46
 
55
- const borderColor = (() => {
56
- if (!!sensor && sensor?.is_managed_by_backend) {
57
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
58
- return Colors.Gray4;
59
- }
60
- if (isBluetoothConnected) {
61
- return Colors.Gray4;
62
- }
63
- if (isFetchingStatusFromEoHBackend || isEoHBackendConnected) {
64
- return Colors.Gray4;
65
- }
66
- return Colors.Red6;
67
- }
68
- // not managed by backend
69
- if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
70
- if (isHomeAssistantConnecting || isHomeAssistantConnected) {
71
- return Colors.Gray4;
72
- }
73
- }
74
- return Colors.Red6;
75
- })();
76
-
77
- const textConnected = (() => {
47
+ const textConnected = useMemo(() => {
78
48
  if (!!sensor && sensor?.is_managed_by_backend) {
79
49
  if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
80
50
  return t('connected');
@@ -100,9 +70,17 @@ const ItemDevice = memo(
100
70
  }
101
71
  }
102
72
  return t('disconnected');
103
- })();
73
+ }, [
74
+ isBluetoothConnected,
75
+ isEoHBackendConnected,
76
+ isFetchingStatusFromEoHBackend,
77
+ isHomeAssistantConnected,
78
+ isHomeAssistantConnecting,
79
+ sensor,
80
+ t,
81
+ ]);
104
82
 
105
- const canRenderQuickAction = (() => {
83
+ const canRenderQuickAction = useMemo(() => {
106
84
  if (!!sensor && sensor?.is_managed_by_backend) {
107
85
  if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
108
86
  return true;
@@ -120,50 +98,52 @@ const ItemDevice = memo(
120
98
  return isHomeAssistantConnected;
121
99
  }
122
100
  return true;
123
- })();
101
+ }, [
102
+ isBluetoothConnected,
103
+ isFetchingStatusFromEoHBackend,
104
+ isHomeAssistantConnected,
105
+ sensor,
106
+ ]);
124
107
 
125
108
  return (
126
- <TouchableWithoutFeedback
127
- onPress={goToSensorDisplay}
128
- accessibilityLabel={`${AccessibilityLabel.SENSOR_NAME}-${sensor?.id}`}
109
+ <ItemDeviceWrapper
110
+ device={sensor}
111
+ unit={unit}
112
+ station={station}
113
+ wrapStyle={wrapStyle}
129
114
  >
130
- <View
131
- style={[styles.container, wrapStyle, { borderColor }]}
132
- accessibilityLabel={AccessibilityLabel.SUB_UNIT_DEVICES}
133
- >
134
- <View style={styles.boxIcon}>
135
- <TouchableOpacity onPress={goToSensorDisplay}>
136
- <IconComponent icon={sensor?.icon_kit || sensor?.icon} />
137
- </TouchableOpacity>
138
- {canRenderQuickAction && (
139
- <ItemQuickAction sensor={sensor} unit={unit} />
140
- )}
141
- </View>
115
+ <View style={styles.boxIcon}>
142
116
  <TouchableOpacity onPress={goToSensorDisplay}>
117
+ <IconComponent icon={sensor?.icon_kit || sensor?.icon} />
118
+ </TouchableOpacity>
119
+ {!!canRenderQuickAction && (
120
+ <ItemQuickAction sensor={sensor} unit={unit} />
121
+ )}
122
+ </View>
123
+ <TouchableOpacity onPress={goToSensorDisplay}>
124
+ <Text
125
+ numberOfLines={1}
126
+ semibold
127
+ size={14}
128
+ color={Colors.Gray9}
129
+ style={styles.lineHeight22}
130
+ >
131
+ {title}
132
+ </Text>
133
+ <View style={styles.descriptionContainer}>
143
134
  <Text
144
135
  numberOfLines={1}
145
136
  semibold
146
- size={14}
147
- color={Colors.Gray9}
148
- style={styles.lineHeight22}
137
+ size={12}
138
+ color={Colors.Gray8}
139
+ style={styles.lineHeight20}
149
140
  >
150
- {title}
141
+ {description || textConnected}
151
142
  </Text>
152
- <View style={styles.descriptionContainer}>
153
- <Text
154
- numberOfLines={1}
155
- semibold
156
- size={12}
157
- color={Colors.Gray8}
158
- style={styles.lineHeight20}
159
- >
160
- {description || textConnected}
161
- </Text>
162
- <IconOutline name="right" size={12} />
163
- </View>
164
- </TouchableOpacity>
165
- </View>
166
- </TouchableWithoutFeedback>
143
+ <IconOutline name="right" size={12} />
144
+ </View>
145
+ </TouchableOpacity>
146
+ </ItemDeviceWrapper>
167
147
  );
168
148
  }
169
149
  );
@@ -171,27 +151,9 @@ const ItemDevice = memo(
171
151
  export default ItemDevice;
172
152
 
173
153
  const styles = StyleSheet.create({
174
- container: {
175
- padding: 12,
176
- borderRadius: 10,
177
- shadowColor: Colors.Shadow,
178
- shadowOffset: {
179
- width: 0,
180
- height: 2,
181
- },
182
- shadowOpacity: 0.1,
183
- shadowRadius: 3,
184
- elevation: 4,
185
- width: DEVICE_SIZE.width,
186
- height: DEVICE_SIZE.height,
187
- backgroundColor: Colors.White,
188
- justifyContent: 'space-between',
189
- marginBottom: 8,
190
- borderWidth: 1,
191
- },
192
154
  boxIcon: {
193
155
  flexDirection: 'row',
194
- justifyContent: 'space-between',
156
+ flex: 1,
195
157
  },
196
158
  descriptionContainer: {
197
159
  flexDirection: 'row',
@@ -0,0 +1,109 @@
1
+ import React, { memo, useCallback, useMemo } from 'react';
2
+ import { StyleSheet, TouchableWithoutFeedback, View } from 'react-native';
3
+ import Routes from '../../utils/Route';
4
+ import { useNavigation } from '@react-navigation/native';
5
+
6
+ import {
7
+ useBluetoothDeviceConnected,
8
+ useEoHBackendDeviceConnected,
9
+ useHomeAssistantDeviceConnected,
10
+ } from '../../hooks/IoT';
11
+
12
+ import { Colors } from '../../configs';
13
+ import {
14
+ AccessibilityLabel,
15
+ DEVICE_SIZE,
16
+ DEVICE_TYPE,
17
+ } from '../../configs/Constants';
18
+
19
+ const ItemDeviceWrapper = memo(
20
+ ({ device, unit, station, wrapStyle, children }) => {
21
+ const navigation = useNavigation();
22
+
23
+ const {
24
+ isConnected: isEoHBackendConnected,
25
+ isFetchingStatus: isFetchingStatusFromEoHBackend,
26
+ } = useEoHBackendDeviceConnected(device);
27
+
28
+ const {
29
+ isConnected: isHomeAssistantConnected,
30
+ isConnecting: isHomeAssistantConnecting,
31
+ } = useHomeAssistantDeviceConnected(device);
32
+
33
+ const { isConnected: isBluetoothConnected } =
34
+ useBluetoothDeviceConnected(device);
35
+
36
+ const goToSensorDisplay = useCallback(() => {
37
+ navigation.navigate(Routes.DeviceDetail, {
38
+ unitData: unit,
39
+ station,
40
+ sensorData: device,
41
+ title: device.name,
42
+ });
43
+ }, [navigation, device, station, unit]);
44
+
45
+ const borderColor = useMemo(() => {
46
+ if (!!device && device?.is_managed_by_backend) {
47
+ if (device?.device_type === DEVICE_TYPE.LG_THINQ) {
48
+ return Colors.Gray4;
49
+ }
50
+ if (isBluetoothConnected) {
51
+ return Colors.Gray4;
52
+ }
53
+ if (isFetchingStatusFromEoHBackend || isEoHBackendConnected) {
54
+ return Colors.Gray4;
55
+ }
56
+ return Colors.Red6;
57
+ }
58
+ // not managed by backend
59
+ if (device?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
60
+ if (isHomeAssistantConnecting || isHomeAssistantConnected) {
61
+ return Colors.Gray4;
62
+ }
63
+ }
64
+ return Colors.Red6;
65
+ }, [
66
+ isBluetoothConnected,
67
+ isEoHBackendConnected,
68
+ isFetchingStatusFromEoHBackend,
69
+ isHomeAssistantConnected,
70
+ isHomeAssistantConnecting,
71
+ device,
72
+ ]);
73
+
74
+ return (
75
+ <TouchableWithoutFeedback
76
+ onPress={goToSensorDisplay}
77
+ accessibilityLabel={`${AccessibilityLabel.SENSOR_NAME}-${device?.id}`}
78
+ >
79
+ <View
80
+ style={[styles.container, wrapStyle, { borderColor }]}
81
+ accessibilityLabel={AccessibilityLabel.SUB_UNIT_DEVICES}
82
+ >
83
+ {children}
84
+ </View>
85
+ </TouchableWithoutFeedback>
86
+ );
87
+ }
88
+ );
89
+
90
+ export default ItemDeviceWrapper;
91
+
92
+ const styles = StyleSheet.create({
93
+ container: {
94
+ padding: 12,
95
+ borderRadius: 10,
96
+ shadowColor: Colors.Shadow,
97
+ shadowOffset: {
98
+ width: 0,
99
+ height: 2,
100
+ },
101
+ shadowOpacity: 0.1,
102
+ shadowRadius: 3,
103
+ elevation: 4,
104
+ width: DEVICE_SIZE.width,
105
+ backgroundColor: Colors.White,
106
+ marginBottom: 8,
107
+ borderWidth: 1,
108
+ },
109
+ });
@@ -0,0 +1,31 @@
1
+ import { useTranslations } from '../../hooks/Common/useTranslations';
2
+ import { timeDifference } from '../../utils/Converter/time';
3
+ import Text from '../Text';
4
+ import { Colors } from '../../configs';
5
+ import React, { memo } from 'react';
6
+ import { StyleSheet } from 'react-native';
7
+
8
+ const styles = StyleSheet.create({
9
+ txtLastUpdate: {
10
+ marginLeft: 8,
11
+ lineHeight: 20,
12
+ marginTop: 8,
13
+ },
14
+ });
15
+
16
+ const LastUpdatedText = memo(({ lastUpdated, showText }) => {
17
+ const t = useTranslations();
18
+
19
+ const lastUpdatedStr = lastUpdated
20
+ ? timeDifference(new Date(), lastUpdated)
21
+ : `5 ${t('seconds_ago')}`;
22
+ return (
23
+ <Text color={Colors.Gray7} size={12} style={styles.txtLastUpdate}>
24
+ {!!showText && t('last_updated')}
25
+ {!!showText && ' '}
26
+ {lastUpdatedStr}
27
+ </Text>
28
+ );
29
+ });
30
+
31
+ export default LastUpdatedText;
@@ -28,7 +28,11 @@ describe('Test ConnectedViewHeader', () => {
28
28
  const instance = tree.root;
29
29
  const texts = instance.findAllByType(Text);
30
30
  expect(texts[0].props.children).toEqual(t('connected_via_internet'));
31
- expect(texts[1].props.children).toEqual(t('last_updated') + ' 2 years ago');
31
+ expect(texts[1].props.children).toEqual([
32
+ t('last_updated'),
33
+ ' ',
34
+ '2 years ago',
35
+ ]);
32
36
  });
33
37
 
34
38
  it('render ConnectedViewHeader no last updated', async () => {
@@ -37,8 +41,10 @@ describe('Test ConnectedViewHeader', () => {
37
41
  });
38
42
  const instance = tree.root;
39
43
  const texts = instance.findAllByType(Text);
40
- expect(texts[1].props.children).toEqual(
41
- t('last_updated') + ' 5 seconds ago'
42
- );
44
+ expect(texts[1].props.children).toEqual([
45
+ t('last_updated'),
46
+ ' ',
47
+ '5 seconds ago',
48
+ ]);
43
49
  });
44
50
  });
@@ -0,0 +1,46 @@
1
+ import React, { memo } from 'react';
2
+ import styles from '../styles/ConfigValueStyles';
3
+ import { View } from 'react-native';
4
+ import Text from '../../../Text';
5
+ import LastUpdatedText from '../../../Device/LastUpdatedText';
6
+ import { EvaluationConfigWrapper } from '../EvaluationOverConfig/EvaluationOverConfig';
7
+
8
+ const ConfigAndEvaluationDisplay = memo(
9
+ ({ valueEvaluation, stationItem, configValue }) => {
10
+ return (
11
+ <>
12
+ <View>
13
+ <View style={styles.rowTop}>
14
+ <Text type="H4" bold>
15
+ {stationItem?.configuration?.config_data?.name}
16
+ </Text>
17
+ </View>
18
+ <Text type="Body" color={valueEvaluation?.color}>
19
+ {valueEvaluation?.text}
20
+ </Text>
21
+ </View>
22
+ <View style={styles.rowBottom}>
23
+ <Text style={styles.textValue} bold>
24
+ {configValue?.value || '-'}
25
+ </Text>
26
+ <Text type="H4">{stationItem?.configuration?.config_data?.unit}</Text>
27
+ </View>
28
+ {configValue?.last_updated && (
29
+ <LastUpdatedText lastUpdated={configValue?.last_updated} />
30
+ )}
31
+ </>
32
+ );
33
+ }
34
+ );
35
+
36
+ const ConfigAndEvaluation = memo(({ device, stationItem }) => {
37
+ return (
38
+ <EvaluationConfigWrapper
39
+ device={device}
40
+ stationItem={stationItem}
41
+ Child={ConfigAndEvaluationDisplay}
42
+ />
43
+ );
44
+ });
45
+
46
+ export default ConfigAndEvaluation;
@@ -0,0 +1,12 @@
1
+ import ConfigAndEvaluation from './ConfigAndEvaluation';
2
+ import { registerTemplate } from '../register';
3
+
4
+ const isWidget = (item) => {
5
+ return item.template === 'ConfigAndEvaluation';
6
+ };
7
+
8
+ const isSettingValid = (configuration) => {
9
+ return configuration.config && configuration.evaluation;
10
+ };
11
+
12
+ registerTemplate(isWidget, isSettingValid, ConfigAndEvaluation, {});
@@ -0,0 +1,41 @@
1
+ import React, { memo, useMemo } from 'react';
2
+ import styles from '../styles/ConfigValueStyles';
3
+ import { View } from 'react-native';
4
+ import Text from '../../../Text';
5
+ import { useConfigGlobalState } from '../../../../iot/states';
6
+ import { useWatchConfigs } from '../../../../hooks/IoT';
7
+ import LastUpdatedText from '../../../Device/LastUpdatedText';
8
+
9
+ const ConfigValue = ({ device, stationItem }) => {
10
+ // eslint-disable-next-line no-unused-vars
11
+ const [configValues, _] = useConfigGlobalState('configValues');
12
+ const configValue = configValues[stationItem.configuration?.config];
13
+ const configIds = useMemo(() => {
14
+ return [stationItem.configuration?.config];
15
+ }, [stationItem.configuration?.config]);
16
+
17
+ useWatchConfigs(configIds);
18
+
19
+ return (
20
+ <>
21
+ <View>
22
+ <View style={styles.rowTop}>
23
+ <Text type="H4" bold>
24
+ {stationItem?.configuration?.config_data?.name}
25
+ </Text>
26
+ </View>
27
+ </View>
28
+ <View style={styles.rowBottom}>
29
+ <Text style={styles.textValue} bold>
30
+ {configValue?.value || '-'}
31
+ </Text>
32
+ <Text type="H4">{stationItem?.configuration?.config_data?.unit}</Text>
33
+ </View>
34
+ {configValue?.last_updated && (
35
+ <LastUpdatedText lastUpdated={configValue?.last_updated} />
36
+ )}
37
+ </>
38
+ );
39
+ };
40
+
41
+ export default memo(ConfigValue);
@@ -0,0 +1,12 @@
1
+ import ConfigValue from './ConfigValue';
2
+ import { registerTemplate } from '../register';
3
+
4
+ const isWidget = (item) => {
5
+ return item.template === 'ConfigValue';
6
+ };
7
+
8
+ const isSettingValid = (configuration) => {
9
+ return configuration.config;
10
+ };
11
+
12
+ registerTemplate(isWidget, isSettingValid, ConfigValue, {});
@@ -0,0 +1,21 @@
1
+ import React, { memo } from 'react';
2
+ import { getTemplate } from './';
3
+ import ItemDeviceWrapper from '../../Device/ItemDeviceWrapper';
4
+
5
+ export const DeviceTemplate = memo(
6
+ ({ device, stationItem, unit, station, index }) => {
7
+ const Template = getTemplate(stationItem);
8
+ if (!Template) {
9
+ return null;
10
+ }
11
+ return (
12
+ <ItemDeviceWrapper device={device} unit={unit} station={station}>
13
+ <Template.Display
14
+ device={device}
15
+ stationItem={stationItem}
16
+ index={index}
17
+ />
18
+ </ItemDeviceWrapper>
19
+ );
20
+ }
21
+ );
@@ -0,0 +1,79 @@
1
+ import React, { memo, useMemo } from 'react';
2
+ import styles from '../styles/ConfigValueStyles';
3
+ import { View } from 'react-native';
4
+ import Text from '../../../Text';
5
+ import { useConfigGlobalState } from '../../../../iot/states';
6
+ import {
7
+ useEvaluateValue,
8
+ useValueEvaluation,
9
+ } from '../../../../screens/Device/hooks/useEvaluateValue';
10
+ import { useWatchConfigs } from '../../../../hooks/IoT';
11
+ import LastUpdatedText from '../../../Device/LastUpdatedText';
12
+
13
+ export const EvaluationConfigWrapper = memo(
14
+ ({ device, stationItem, Child }) => {
15
+ // eslint-disable-next-line no-unused-vars
16
+ const [configValues, _] = useConfigGlobalState('configValues');
17
+ const configValue = configValues[stationItem.configuration?.config];
18
+ const configIds = useMemo(() => {
19
+ return [stationItem.configuration?.config];
20
+ }, [stationItem.configuration?.config]);
21
+
22
+ useWatchConfigs(configIds);
23
+
24
+ const valueEvaluationObject = useValueEvaluation(
25
+ stationItem.configuration?.config,
26
+ device.unit_id,
27
+ stationItem.configuration?.evaluation
28
+ );
29
+
30
+ const valueEvaluation = useEvaluateValue()(
31
+ configValue?.value,
32
+ valueEvaluationObject
33
+ );
34
+
35
+ return (
36
+ <Child
37
+ valueEvaluation={valueEvaluation}
38
+ stationItem={stationItem}
39
+ configValue={configValue}
40
+ />
41
+ );
42
+ }
43
+ );
44
+
45
+ const EvaluationOverConfigDisplay = memo(
46
+ ({ valueEvaluation, stationItem, configValue }) => {
47
+ return (
48
+ <>
49
+ <View>
50
+ <View style={styles.rowTop}>
51
+ <Text type="H4" bold>
52
+ {stationItem?.configuration?.config_data?.name}
53
+ </Text>
54
+ </View>
55
+ </View>
56
+ <View style={styles.rowBottom}>
57
+ <Text style={styles.textValue} bold color={valueEvaluation?.color}>
58
+ {valueEvaluation?.text}
59
+ </Text>
60
+ </View>
61
+ {configValue?.last_updated && (
62
+ <LastUpdatedText lastUpdated={configValue?.last_updated} />
63
+ )}
64
+ </>
65
+ );
66
+ }
67
+ );
68
+
69
+ const EvaluationOverConfig = memo(({ device, stationItem }) => {
70
+ return (
71
+ <EvaluationConfigWrapper
72
+ device={device}
73
+ stationItem={stationItem}
74
+ Child={EvaluationOverConfigDisplay}
75
+ />
76
+ );
77
+ });
78
+
79
+ export default EvaluationOverConfig;
@@ -0,0 +1,12 @@
1
+ import EvaluationOverConfig from './EvaluationOverConfig';
2
+ import { registerTemplate } from '../register';
3
+
4
+ const isWidget = (item) => {
5
+ return item.template === 'EvaluationOverConfig';
6
+ };
7
+
8
+ const isSettingValid = (configuration) => {
9
+ return configuration.config && configuration.evaluation;
10
+ };
11
+
12
+ registerTemplate(isWidget, isSettingValid, EvaluationOverConfig, {});
@@ -0,0 +1,9 @@
1
+ import { DeviceItemTemplates } from './register';
2
+
3
+ export * from './ConfigAndEvaluation';
4
+ export * from './ConfigValue';
5
+ export * from './EvaluationOverConfig';
6
+
7
+ export const getTemplate = (item) => {
8
+ return DeviceItemTemplates.find((w) => w.detect(item));
9
+ };
@@ -0,0 +1,14 @@
1
+ export const DeviceItemTemplates = [];
2
+ export const registerTemplate = (
3
+ isWidget,
4
+ isSettingValid,
5
+ DisplayComponent,
6
+ defaultConfiguration
7
+ ) => {
8
+ DeviceItemTemplates.push({
9
+ detect: isWidget,
10
+ isSettingValid: isSettingValid,
11
+ Display: DisplayComponent,
12
+ defaultConfiguration,
13
+ });
14
+ };
@@ -0,0 +1,23 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ rowTop: {
5
+ flexDirection: 'row',
6
+ justifyContent: 'space-between',
7
+ alignItems: 'center',
8
+ },
9
+ rowBottom: {
10
+ flexDirection: 'row',
11
+ alignItems: 'flex-end',
12
+ },
13
+ batteryStatus: {
14
+ flexDirection: 'row',
15
+ justifyContent: 'space-between',
16
+ alignItems: 'center',
17
+ },
18
+ textValue: {
19
+ fontSize: 28,
20
+ lineHeight: 32,
21
+ marginRight: 8,
22
+ },
23
+ });
@@ -16,6 +16,7 @@ import { standardizeCameraScreenSize } from '../../utils/Utils';
16
16
  import Routes from '../../utils/Route';
17
17
  import FastImage from 'react-native-fast-image';
18
18
  import MediaPlayerDetail from '../MediaPlayerDetail';
19
+ import { DeviceTemplate } from './DeviceTemplate/DeviceTemplate';
19
20
 
20
21
  const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
21
22
  Device.screenWidth - 32
@@ -25,15 +26,12 @@ const ShortDetailSubUnit = ({ unit, station, isOwner }) => {
25
26
  const t = useTranslations();
26
27
  const { navigate } = useNavigation();
27
28
 
28
- useDevicesStatus(unit, station?.sensors);
29
+ useDevicesStatus(unit, station?.devices);
29
30
 
30
31
  const configsNeedWatching = useMemo(() => {
31
32
  const configIds = [];
32
- (station?.sensors || []).forEach((device) => {
33
- if (
34
- device?.quick_action?.config_id &&
35
- device?.device_type !== DEVICE_TYPE.GOOGLE_HOME
36
- ) {
33
+ (station?.devices || []).forEach((device) => {
34
+ if (device?.quick_action?.config_id && device?.is_managed_by_backend) {
37
35
  configIds.push(device.quick_action.config_id);
38
36
  }
39
37
  });
@@ -108,51 +106,79 @@ const ShortDetailSubUnit = ({ unit, station, isOwner }) => {
108
106
  });
109
107
  };
110
108
 
109
+ const renderSubUnitItem = (device, stationItem, index) => {
110
+ return (
111
+ <DeviceTemplate
112
+ device={device}
113
+ stationItem={stationItem}
114
+ index={index}
115
+ key={index}
116
+ unit={unit}
117
+ station={station}
118
+ />
119
+ );
120
+ };
121
+
122
+ const renderDeviceList = () => {
123
+ if (!station.devices) {
124
+ return [];
125
+ }
126
+
127
+ return station.devices
128
+ .map((device, index) => {
129
+ const displays = [];
130
+ if (device.device_type === DEVICE_TYPE.HANET) {
131
+ displays.push(
132
+ <ItemHanetDevice
133
+ key={`sensor-${device.id}`}
134
+ id={device.id}
135
+ svgMain={device.icon || 'sensor'}
136
+ title={device.name}
137
+ index={index}
138
+ sensor={device}
139
+ unit={unit}
140
+ station={station}
141
+ />
142
+ );
143
+ } else {
144
+ displays.push(
145
+ <ItemDevice
146
+ key={`sensor-${device.id}`}
147
+ id={device.id}
148
+ svgMain={device.icon || 'sensor'}
149
+ statusIcon={device.action && device.action.icon}
150
+ statusColor={device.action && device.action.color}
151
+ description={device.value}
152
+ title={device.name}
153
+ index={index}
154
+ sensor={device}
155
+ unit={unit}
156
+ station={station}
157
+ />
158
+ );
159
+ }
160
+ if (device.station_items) {
161
+ displays.push(
162
+ ...device.station_items.map(renderSubUnitItem.bind(device, device))
163
+ );
164
+ }
165
+ return displays;
166
+ })
167
+ .flat();
168
+ };
169
+
111
170
  return (
112
171
  <Section style={styles.noShadow}>
113
172
  {renderCamera()}
114
173
 
115
- {!!station?.sensors?.length && (
174
+ {!!station?.devices?.length && (
116
175
  <Text type={'H4'} semibold style={styles.device}>{`${t('device')} (${
117
- station?.sensors?.length
176
+ station?.devices?.length
118
177
  })`}</Text>
119
178
  )}
120
179
 
121
180
  <View style={styles.boxDevices}>
122
- {!!station.sensors &&
123
- station.sensors.map((sensor, index) => {
124
- switch (sensor.device_type) {
125
- case DEVICE_TYPE.HANET:
126
- return (
127
- <ItemHanetDevice
128
- key={`sensor-${sensor.id}`}
129
- id={sensor.id}
130
- svgMain={sensor.icon || 'sensor'}
131
- title={sensor.name}
132
- index={index}
133
- sensor={sensor}
134
- unit={unit}
135
- station={station}
136
- />
137
- );
138
- default:
139
- return (
140
- <ItemDevice
141
- key={`sensor-${sensor.id}`}
142
- id={sensor.id}
143
- svgMain={sensor.icon || 'sensor'}
144
- statusIcon={sensor.action && sensor.action.icon}
145
- statusColor={sensor.action && sensor.action.color}
146
- description={sensor.value}
147
- title={sensor.name}
148
- index={index}
149
- sensor={sensor}
150
- unit={unit}
151
- station={station}
152
- />
153
- );
154
- }
155
- })}
181
+ {renderDeviceList()}
156
182
  {isOwner && (
157
183
  <ItemAddNew title={t('add_new_device')} onAddNew={handleOnAddNew} />
158
184
  )}
@@ -9,6 +9,12 @@ import ShortDetailSubUnit from '../ShortDetail';
9
9
  import ItemAddNew from '../../Device/ItemAddNew';
10
10
  import Routes from '../../../utils/Route';
11
11
  import { keyPermission } from '../../../utils/Permission/common';
12
+ import ItemDevice from '../../Device/ItemDevice';
13
+ import { DeviceTemplate } from '../DeviceTemplate/DeviceTemplate';
14
+ import ItemHanetDevice from '../../Device/Hanet/ItemHanetDevice';
15
+ import { watchMultiConfigs } from '../../../iot/Monitor';
16
+
17
+ jest.mock('../../../iot/Monitor');
12
18
 
13
19
  const wrapComponent = (props) => (
14
20
  <SCProvider initState={mockSCStore({})}>
@@ -28,7 +34,7 @@ jest.mock('@react-navigation/native', () => {
28
34
  navigate: mockedNavigate,
29
35
  }),
30
36
  useIsFocused: () => true,
31
- useFocusEffect: jest.fn(),
37
+ useFocusEffect: jest.fn((handler) => handler()),
32
38
  };
33
39
  });
34
40
 
@@ -53,7 +59,7 @@ describe('test ShortDetail Subunit', () => {
53
59
  camera: null,
54
60
  id: 71,
55
61
  name: 'Station 1',
56
- sensors: [],
62
+ devices: [],
57
63
  },
58
64
  ],
59
65
  user_id: 64,
@@ -120,7 +126,7 @@ describe('test ShortDetail Subunit', () => {
120
126
  });
121
127
 
122
128
  it('render ShortDetail with device', async () => {
123
- props.station.sensors = [
129
+ props.station.devices = [
124
130
  {
125
131
  action: {
126
132
  color: '#00979D',
@@ -146,12 +152,110 @@ describe('test ShortDetail Subunit', () => {
146
152
  tree = await create(wrapComponent(props));
147
153
  });
148
154
  const instance = tree.root;
149
- const itemDevice = instance.findAll(
150
- (item) =>
151
- item.props.accessibilityLabel === AccessibilityLabel.SUB_UNIT_DEVICES &&
152
- item.type === View
153
- );
154
- expect(itemDevice.length).toBe(0);
155
+ const itemDevices = instance.findAllByType(ItemDevice);
156
+ expect(itemDevices.length).toBe(1);
157
+ });
158
+
159
+ it('render watch config if managed by backend', async () => {
160
+ props.station.devices = [
161
+ {
162
+ action: {
163
+ color: '#00979D',
164
+ icon: 'caret-up',
165
+ id: 1,
166
+ key: '',
167
+ },
168
+ action2: null,
169
+ chip_id: 1,
170
+ description: null,
171
+ icon: '',
172
+ id: 1,
173
+ name: 'People Counting',
174
+ quick_action: { config_id: 1 },
175
+ remote_control_options: {},
176
+ station: {},
177
+ status: null,
178
+ status2: null,
179
+ is_managed_by_backend: true,
180
+ },
181
+ ];
182
+
183
+ await act(async () => {
184
+ tree = await create(wrapComponent(props));
185
+ });
186
+ expect(watchMultiConfigs).toBeCalledWith([1]);
187
+ });
188
+
189
+ ['ConfigAndEvaluation', 'ConfigValue', 'EvaluationOverConfig'].forEach(
190
+ (template) => {
191
+ it(`render with device template ${template}`, async () => {
192
+ props.station.devices = [
193
+ {
194
+ action: {
195
+ color: '#00979D',
196
+ icon: 'caret-up',
197
+ id: 1,
198
+ key: '',
199
+ },
200
+ action2: null,
201
+ chip_id: 1,
202
+ description: null,
203
+ icon: '',
204
+ id: 1,
205
+ name: 'People Counting',
206
+ quick_action: null,
207
+ remote_control_options: {},
208
+ station: {},
209
+ status: null,
210
+ status2: null,
211
+ station_items: [
212
+ {
213
+ template: template,
214
+ },
215
+ ],
216
+ },
217
+ ];
218
+
219
+ await act(async () => {
220
+ tree = await create(wrapComponent(props));
221
+ });
222
+ const instance = tree.root;
223
+ const itemDevices = instance.findAllByType(DeviceTemplate);
224
+ expect(itemDevices.length).toBe(1);
225
+ });
226
+ }
227
+ );
228
+
229
+ it('render with device hanet', async () => {
230
+ props.station.devices = [
231
+ {
232
+ device_type: 'HANET',
233
+ action: {
234
+ color: '#00979D',
235
+ icon: 'caret-up',
236
+ id: 1,
237
+ key: '',
238
+ },
239
+ action2: null,
240
+ chip_id: 1,
241
+ description: null,
242
+ icon: '',
243
+ id: 1,
244
+ name: 'People Counting',
245
+ quick_action: null,
246
+ remote_control_options: {},
247
+ station: {},
248
+ status: null,
249
+ status2: null,
250
+ },
251
+ ];
252
+
253
+ await act(async () => {
254
+ tree = await create(wrapComponent(props));
255
+ });
256
+ const instance = tree.root;
257
+ const itemDevices = instance.findAllByType(ItemHanetDevice);
258
+ expect(itemDevices.length).toBe(1);
155
259
  });
156
260
 
157
261
  it('render ShortDetail add new device', async () => {
@@ -628,12 +628,8 @@ describe('test DeviceDetail', () => {
628
628
  await act(async () => {
629
629
  tree = await create(wrapComponent(store, account, route));
630
630
  });
631
- const instance = tree.root;
632
- const sensorDisplayItem = instance.findAll(
633
- (el) =>
634
- el.props.accessibilityLabel === AccessibilityLabel.SENSOR_DISPLAY_ITEM
635
- );
636
- expect(sensorDisplayItem).toHaveLength(0);
631
+ const urls = mock.history.get.map((x) => x.url);
632
+ expect(urls).not.toContain(API.DEVICE.DISPLAY_VALUES_V2(1));
637
633
  });
638
634
 
639
635
  it('HeaderDevice button more onClick', async () => {
@@ -30,68 +30,72 @@ export const SensorConnectStatusViewHeader = ({
30
30
  return <></>;
31
31
  }
32
32
 
33
- if (!!sensor && sensor?.is_managed_by_backend) {
34
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
35
- return (
36
- <>
37
- <ConnectedViewHeader lastUpdated={lastUpdated} />
38
- {children}
39
- </>
40
- );
41
- }
42
- if (connectedViaNetwork) {
43
- return (
44
- <>
45
- <ConnectedViewHeader
46
- lastUpdated={lastUpdated}
47
- isDisplayTime={isDisplayTime}
48
- showWindDirection={showWindDirection}
49
- />
50
- {children}
51
- </>
52
- );
53
- } else if (connectedViaBle) {
54
- return (
55
- <>
56
- <ConnectedViewHeader
57
- lastUpdated={lastUpdated}
58
- type={'Bluetooth'}
59
- isDisplayTime={isDisplayTime}
60
- showWindDirection={showWindDirection}
61
- />
62
- {children}
63
- </>
64
- );
65
- } else {
66
- return (
67
- <DisconnectedView sensor={sensor} isDeviceHasBle={isDeviceHasBle} />
68
- );
69
- }
70
- } else {
71
- // not managed by backend
72
- if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
73
- if (connectedViaHomeAssistant) {
33
+ const header = () => {
34
+ if (!!sensor && sensor?.is_managed_by_backend) {
35
+ if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
36
+ return (
37
+ <>
38
+ <ConnectedViewHeader lastUpdated={lastUpdated} />
39
+ </>
40
+ );
41
+ }
42
+ if (connectedViaNetwork) {
74
43
  return (
75
44
  <>
76
45
  <ConnectedViewHeader
77
46
  lastUpdated={lastUpdated}
78
47
  isDisplayTime={isDisplayTime}
79
- type={'HomeAssistant'}
48
+ showWindDirection={showWindDirection}
49
+ />
50
+ </>
51
+ );
52
+ } else if (connectedViaBle) {
53
+ return (
54
+ <>
55
+ <ConnectedViewHeader
56
+ lastUpdated={lastUpdated}
57
+ type={'Bluetooth'}
58
+ isDisplayTime={isDisplayTime}
59
+ showWindDirection={showWindDirection}
80
60
  />
81
- {children}
82
61
  </>
83
62
  );
84
63
  } else {
85
64
  return (
86
- <DisconnectedView
87
- sensor={sensor}
88
- type={'HomeAssistant'}
89
- isDeviceHasBle={isDeviceHasBle}
90
- />
65
+ <DisconnectedView sensor={sensor} isDeviceHasBle={isDeviceHasBle} />
91
66
  );
92
67
  }
93
68
  } else {
94
- return <DisconnectedView sensor={sensor} />;
69
+ // not managed by backend
70
+ if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
71
+ if (connectedViaHomeAssistant) {
72
+ return (
73
+ <>
74
+ <ConnectedViewHeader
75
+ lastUpdated={lastUpdated}
76
+ isDisplayTime={isDisplayTime}
77
+ type={'HomeAssistant'}
78
+ />
79
+ </>
80
+ );
81
+ } else {
82
+ return (
83
+ <DisconnectedView
84
+ sensor={sensor}
85
+ type={'HomeAssistant'}
86
+ isDeviceHasBle={isDeviceHasBle}
87
+ />
88
+ );
89
+ }
90
+ } else {
91
+ return <DisconnectedView sensor={sensor} />;
92
+ }
95
93
  }
96
- }
94
+ };
95
+ return (
96
+ <>
97
+ {header()}
98
+ {children}
99
+ </>
100
+ );
97
101
  };
@@ -517,7 +517,9 @@ const DeviceDetail = ({ route }) => {
517
517
  !configIds.includes(configId) && configIds.push(configId);
518
518
  }
519
519
  });
520
- configIdsTemp.current = configIds.filter(Boolean);
520
+
521
+ configIds = configIds.filter(Boolean);
522
+ configIdsTemp.current = configIds;
521
523
 
522
524
  configIds.map((id) => {
523
525
  params.append('config', id);
@@ -146,3 +146,8 @@ export const useGetEvaluateValue = (configId, unitId) => {
146
146
 
147
147
  return valueEvaluations[configId];
148
148
  };
149
+
150
+ export const useValueEvaluation = (configId, unitId, valueEvaluationId) => {
151
+ const valueEvaluations = useGetEvaluateValue(configId, unitId);
152
+ return valueEvaluations?.find((v) => v.id === valueEvaluationId);
153
+ };
@@ -20,8 +20,8 @@ const wrapComponent = (item) => (
20
20
  </SCProvider>
21
21
  );
22
22
 
23
- describe('test DeviceItem', () => {
24
- it('test DeviceItem', async () => {
23
+ describe('test DeviceTemplate', () => {
24
+ it('test DeviceTemplate', async () => {
25
25
  const item = {
26
26
  id: 1,
27
27
  name: 'abc',
@@ -11,7 +11,7 @@ const wrapComponent = (props) => (
11
11
  </SCProvider>
12
12
  );
13
13
 
14
- it('test DeviceItem', async () => {
14
+ it('test DeviceTemplate', async () => {
15
15
  let tree;
16
16
  let props = {
17
17
  icon: '',
@@ -8,6 +8,10 @@ export const timeDifference = (current, previous, symbol = false) => {
8
8
  let msPerMonth = msPerDay * 30;
9
9
  let msPerYear = msPerDay * 365;
10
10
 
11
+ if (typeof previous === 'string') {
12
+ previous = moment(previous).toDate();
13
+ }
14
+
11
15
  let elapsed = current - previous;
12
16
 
13
17
  if (elapsed < msPerMinute) {