@eohjsc/react-native-smart-city 0.5.0 → 0.5.1

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/package.json +2 -2
  2. package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplateStyle.js +0 -1
  3. package/src/commons/ActionGroup/OnOffTemplate/SwitchButtonTemplate.js +60 -48
  4. package/src/commons/ActionGroup/OnOffTemplate/index.js +1 -2
  5. package/src/commons/ActionGroup/SliderRangeTemplate.js +53 -64
  6. package/src/commons/ActionGroup/SliderRangeTemplateStyles.js +2 -4
  7. package/src/commons/ActionGroup/TerminalBoxTemplate.js +6 -6
  8. package/src/commons/ActionGroup/TextBoxTemplate.js +2 -2
  9. package/src/commons/ActionGroup/__test__/SliderRangeTemplate.test.js +69 -17
  10. package/src/commons/ActionGroup/__test__/SwitchButtonTemplate.test.js +33 -25
  11. package/src/commons/ActionGroup/__test__/TextBoxTemplate.test.js +19 -0
  12. package/src/commons/Device/RainningSensor/CurrentRainSensor.js +50 -45
  13. package/src/commons/Form/TextInput.js +2 -0
  14. package/src/commons/Header/Styles/HeaderCustomStyles.js +1 -1
  15. package/src/commons/HeaderAni/index.js +1 -1
  16. package/src/commons/Sharing/WrapHeaderScrollable.js +1 -2
  17. package/src/screens/Automate/AddNewAction/SetupScriptDelay.js +1 -1
  18. package/src/screens/Automate/Components/InputName.js +8 -2
  19. package/src/screens/Automate/EditActionsList/index.js +5 -5
  20. package/src/screens/Automate/OneTap/__test__/AddNewOneTap.test.js +17 -3
  21. package/src/screens/Device/components/SensorDisplayItem.js +7 -3
  22. package/src/screens/Device/detail.js +1 -1
  23. package/src/screens/Unit/ManageUnitStyles.js +1 -1
  24. package/src/utils/I18n/translations/en.js +2 -0
  25. package/src/utils/I18n/translations/vi.js +2 -0
  26. package/src/commons/ActionGroup/OnOffTemplate/styles.js +0 -7
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.5.00",
4
+ "version": "0.5.01",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -198,7 +198,7 @@
198
198
  "react-native-toast-message": "^2.1.1",
199
199
  "react-native-udp": "4.1.3",
200
200
  "react-native-version-check": "^3.4.2",
201
- "react-native-vlc-media-player": "^1.0.41",
201
+ "react-native-vlc-media-player": "^1.0.67",
202
202
  "react-native-webview": "11.22.7",
203
203
  "react-native-wheel-color-picker": "^1.2.0",
204
204
  "react-native-wheel-scrollview-picker": "^1.2.2",
@@ -7,7 +7,6 @@ export default StyleSheet.create({
7
7
  justifyContent: 'center',
8
8
  alignItems: 'center',
9
9
  padding: 20,
10
- paddingVertical: 50,
11
10
  },
12
11
  bigCircle: {
13
12
  backgroundColor: Colors.Gray2,
@@ -1,4 +1,4 @@
1
- import React, { memo, useCallback, useEffect, useState } from 'react';
1
+ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { Switch, View } from 'react-native';
3
3
 
4
4
  import Text from '../../Text';
@@ -7,10 +7,9 @@ import styles from './OnOffButtonTemplateStyle';
7
7
  import AccessibilityLabel from '../../../configs/AccessibilityLabel';
8
8
  import { useConfigGlobalState } from '../../../iot/states';
9
9
 
10
- const SwitchButtonTemplate = memo(
11
- ({ item = {}, isLight = false, doAction }) => {
12
- const { configuration = {}, id } = item;
13
- const {
10
+ const SwitchButtonTemplate = memo(({ item, doAction }) => {
11
+ const {
12
+ configuration: {
14
13
  action_off_data,
15
14
  action_on_data,
16
15
  text_on,
@@ -18,54 +17,67 @@ const SwitchButtonTemplate = memo(
18
17
  color_on,
19
18
  color_off,
20
19
  config,
21
- } = configuration;
20
+ is_on_value,
21
+ } = {},
22
+ id,
23
+ } = item;
22
24
 
23
- const [configValues] = useConfigGlobalState('configValues');
24
- const configValue = configValues[config];
25
- const [_isOn, setIsOn] = useState(configValue?.value);
25
+ const [configValues] = useConfigGlobalState('configValues');
26
+ const configValue = configValues[config]?.value;
27
+ const [isOn, setIsOn] = useState();
28
+ const getIsOnValue = useCallback(() => {
29
+ if (is_on_value && is_on_value.length > 0) {
30
+ return is_on_value.includes(configValue);
31
+ }
32
+ return !!configValue;
33
+ }, [configValue, is_on_value]);
26
34
 
27
- const onChangeSwitch = useCallback(() => {
28
- if (_isOn) {
29
- setIsOn(false);
35
+ useEffect(() => {
36
+ setIsOn(getIsOnValue());
37
+ }, [getIsOnValue]);
38
+
39
+ const onValueChange = useCallback(() => {
40
+ setIsOn((prevIsOn) => {
41
+ const newState = !prevIsOn;
42
+ if (newState) {
43
+ doAction(action_on_data, null);
44
+ } else {
30
45
  doAction(action_off_data, null);
31
- return;
32
46
  }
33
- doAction(action_on_data, null);
34
- setIsOn(true);
35
- }, [action_off_data, action_on_data, doAction, _isOn]);
36
-
37
- useEffect(() => {
38
- configValue && setIsOn(configValue?.value);
39
- }, [configValue]);
47
+ return newState;
48
+ });
49
+ }, [action_off_data, action_on_data, doAction]);
40
50
 
41
- return (
42
- <>
43
- <View style={styles.barrierControlContainer}>
44
- <Switch
45
- trackColor={{ false: color_off, true: color_on }}
46
- ios_backgroundColor={_isOn ? color_on : color_off}
47
- onValueChange={onChangeSwitch}
48
- value={!!_isOn}
49
- />
50
- <Text
51
- style={[
52
- styles.textBig,
53
- { color: _isOn ? Colors.Gray8 : Colors.Gray6 },
54
- ]}
55
- accessibilityLabel={`${AccessibilityLabel.SENSOR_STATUS}-${id}`}
56
- >
57
- {_isOn ? text_on : text_off}
58
- </Text>
59
- </View>
51
+ const trackColor = useMemo(
52
+ () => ({ false: color_off, true: color_on }),
53
+ [color_off, color_on]
54
+ );
55
+ const backgroundColor = useMemo(
56
+ () => (isOn ? color_on : color_off),
57
+ [color_off, color_on, isOn]
58
+ );
60
59
 
61
- {!!item.title && !isLight && (
62
- <Text type="H3" semibold center>
63
- {item.title}
64
- </Text>
65
- )}
66
- </>
67
- );
68
- }
69
- );
60
+ return (
61
+ <>
62
+ <View style={styles.barrierControlContainer}>
63
+ <Switch
64
+ trackColor={trackColor}
65
+ ios_backgroundColor={backgroundColor}
66
+ onValueChange={onValueChange}
67
+ value={isOn}
68
+ />
69
+ <Text
70
+ style={[
71
+ styles.textBig,
72
+ { color: isOn ? Colors.Gray8 : Colors.Gray6 },
73
+ ]}
74
+ accessibilityLabel={`${AccessibilityLabel.SENSOR_STATUS}-${id}`}
75
+ >
76
+ {isOn ? text_on : text_off}
77
+ </Text>
78
+ </View>
79
+ </>
80
+ );
81
+ });
70
82
 
71
83
  export default SwitchButtonTemplate;
@@ -7,7 +7,6 @@ import { useConfigGlobalState } from '../../../iot/states';
7
7
  import { useUnwatchLGDeviceConfigControl } from '../../../hooks/IoT';
8
8
  import OnOffButtonTemplate from './OnOffButtonTemplate';
9
9
  import OnOffSimpleTemplate from './OnOffSimpleTemplate';
10
- import styles from './styles';
11
10
 
12
11
  const getComponent = (template) => {
13
12
  switch (template) {
@@ -97,7 +96,7 @@ const OnOffTemplate = memo(({ item = {}, doAction, sensor = {} }) => {
97
96
  }, [item.template]);
98
97
 
99
98
  return (
100
- <View style={styles.wrap}>
99
+ <View>
101
100
  <Component
102
101
  isOn={isOn}
103
102
  triggerAction={triggerAction}
@@ -1,15 +1,7 @@
1
- import React, {
2
- memo,
3
- useCallback,
4
- useState,
5
- useEffect,
6
- useMemo,
7
- useRef,
8
- } from 'react';
1
+ import React, { memo, useCallback, useState, useEffect } from 'react';
9
2
  import { Slider } from '@miblanchard/react-native-slider';
10
3
 
11
4
  import { View } from 'react-native';
12
- import { useTranslations } from '../../hooks/Common/useTranslations';
13
5
  import styles from './SliderRangeTemplateStyles';
14
6
  import Text from '../Text';
15
7
  import { Colors } from '../../configs';
@@ -18,101 +10,98 @@ import { DEVICE_TYPE } from '../../configs/Constants';
18
10
  import _TextInput from '../Form/TextInput';
19
11
 
20
12
  const SliderRangeTemplate = memo(
21
- ({ item = {}, doAction, sensor, isWidgetOrder }) => {
22
- const t = useTranslations();
23
- const { configuration = {}, title, label } = item;
13
+ ({ item, doAction, sensor, isWidgetOrder }) => {
14
+ const { configuration, label = 'Slider range' } = item;
24
15
  const [configValues] = useConfigGlobalState('configValues');
25
- const {
26
- config = undefined,
27
- min_value = 0,
28
- max_value = 100,
29
- action_data,
30
- } = configuration;
31
- const timeout = useRef(null);
16
+ const { config, min_value, max_value, action_data } = configuration;
17
+ const [value, setValue] = useState();
18
+ const [processing, setProcessing] = useState(false);
32
19
 
33
- const getPercent = useCallback(() => {
34
- return configValues[config]?.value || 0;
35
- }, [config, configValues]);
20
+ const onSlidingStart = useCallback(() => {
21
+ setProcessing(true);
22
+ }, []);
36
23
 
37
- const [valueBrightness, setValueBrightness] = useState(getPercent());
24
+ const onSlidingChange = useCallback(async (number) => {
25
+ setValue(number[0]);
26
+ }, []);
38
27
 
39
- const onChangeBrightness = useCallback(
40
- async (value) => {
41
- const value_brness = value[0];
28
+ const onSlidingComplete = useCallback(
29
+ async (number) => {
42
30
  await doAction(
43
31
  action_data,
44
- JSON.stringify({ value_brness: value_brness, value: value_brness })
32
+ JSON.stringify({ value_brness: number[0], value: number[0] })
45
33
  );
34
+ setProcessing(false);
46
35
  },
47
36
  [action_data, doAction]
48
37
  );
49
38
 
50
- useEffect(() => {
51
- const value = configValues[config]?.value || 0;
52
- if (sensor?.device_type !== DEVICE_TYPE.LG_THINQ) {
53
- setValueBrightness(value);
54
- }
55
- }, [config, configValues, sensor?.device_type]);
39
+ const onFocus = useCallback(() => {
40
+ setProcessing(true);
41
+ }, []);
56
42
 
57
- const getTextInputValue = useMemo(() => {
58
- if ([null, undefined, NaN].includes(valueBrightness)) {
59
- /* istanbul ignore next */
60
- return 0;
43
+ const onEndEditing = useCallback(async () => {
44
+ if (value) {
45
+ const data = parseInt(value, 10);
46
+ await doAction(
47
+ action_data,
48
+ JSON.stringify({
49
+ value_brness: data,
50
+ value: data,
51
+ })
52
+ );
61
53
  }
54
+ setProcessing(false);
55
+ }, [action_data, doAction, value]);
62
56
 
63
- return valueBrightness;
64
- }, [valueBrightness]);
65
-
66
- const onValueChange = (value) => setValueBrightness(value[0]);
57
+ useEffect(() => {
58
+ if (!processing) {
59
+ if (sensor?.device_type !== DEVICE_TYPE.LG_THINQ) {
60
+ const configValue = configValues[config]?.value || 0;
61
+ setValue(configValue);
62
+ }
63
+ }
64
+ }, [config, configValues, processing, sensor?.device_type]);
67
65
 
68
- const onInputChange = (value) => {
69
- if (isNaN(value)) {
66
+ const onInputChange = (text) => {
67
+ if (text === '' || isNaN(text)) {
68
+ setValue(min_value);
70
69
  return;
71
70
  }
72
- setValueBrightness(value);
73
- if (timeout.current) {
74
- /* istanbul ignore next */
75
- clearTimeout(timeout.current);
76
- }
77
- if (value !== '') {
78
- timeout.current = setTimeout(() => {
79
- doAction(
80
- action_data,
81
- JSON.stringify({
82
- value_brness: parseInt(value, 10),
83
- value: parseInt(value, 10),
84
- })
85
- );
86
- }, 300);
87
- }
71
+
72
+ const numericValue = Number(text);
73
+ setValue(Math.min(max_value, Math.max(min_value, numericValue)));
88
74
  };
89
75
 
90
76
  return (
91
77
  <View
92
78
  style={(isWidgetOrder && styles.wrapOrderItem) || styles.viewBrightness}
93
79
  >
94
- <Text type="H4">{title || label || t('brightness')}</Text>
80
+ <Text type="H4">{label}</Text>
95
81
  <View style={styles.wrap}>
96
82
  <Slider
97
83
  step={1}
98
84
  minimumValue={min_value}
99
85
  maximumValue={max_value}
100
- value={valueBrightness < min_value ? min_value : valueBrightness}
101
- onValueChange={onValueChange}
102
- onSlidingComplete={onChangeBrightness}
86
+ value={value}
103
87
  minimumTrackTintColor={Colors.Primary}
104
88
  maximumTrackTintColor={Colors.Gray3}
105
89
  trackStyle={styles.trackSlider}
106
90
  thumbStyle={styles.thumbSlider}
107
91
  containerStyle={styles.slider}
92
+ onSlidingStart={onSlidingStart}
93
+ onValueChange={onSlidingChange}
94
+ onSlidingComplete={onSlidingComplete}
108
95
  />
109
96
  <_TextInput
110
97
  keyboardType="numeric"
111
98
  style={styles.text}
112
99
  wrapStyle={styles.wrapInputStyle}
113
100
  textInputStyle={styles.textInputStyle}
114
- value={getTextInputValue.toString()}
101
+ value={String(value)}
102
+ onFocus={onFocus}
115
103
  onChange={onInputChange}
104
+ onEndEditing={onEndEditing}
116
105
  />
117
106
  </View>
118
107
  </View>
@@ -29,11 +29,11 @@ export default StyleSheet.create({
29
29
  marginTop: 8,
30
30
  },
31
31
  slider: {
32
- width: '77%',
32
+ width: '75%',
33
33
  },
34
34
  wrapInputStyle: {
35
35
  marginTop: 0,
36
- width: '23%',
36
+ width: '25%',
37
37
  },
38
38
  textInputStyle: {
39
39
  marginTop: 0,
@@ -41,8 +41,6 @@ export default StyleSheet.create({
41
41
  borderRadius: 4,
42
42
  paddingTop: 5,
43
43
  paddingBottom: 5,
44
- paddingHorizontal: 20,
45
-
46
44
  shadowColor: Colors.Gray13,
47
45
  borderColor: Colors.Gray5,
48
46
  borderWidth: 1,
@@ -96,8 +96,8 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
96
96
  const type = configMessage.id === from_config.id ? 'from' : 'to';
97
97
  messages = [
98
98
  ...messages,
99
- ...configMessage.data.map((item) => {
100
- return { x: item.x, y: item.y, type };
99
+ ...configMessage.data.map((object) => {
100
+ return { x: object.x, y: object.y, type };
101
101
  }),
102
102
  ];
103
103
  });
@@ -147,14 +147,14 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
147
147
  }
148
148
  style={styles.scrollView}
149
149
  >
150
- {allMessages.map((item, index) => {
150
+ {allMessages.map((object, index) => {
151
151
  return (
152
152
  <View
153
153
  key={index}
154
- style={item.type === 'to' ? styles.to : styles.from}
154
+ style={object.type === 'to' ? styles.to : styles.from}
155
155
  >
156
- <Text>{item.y}</Text>
157
- <Text>{moment(item.x).format('DD/MM/YYYY HH:mm:ss')}</Text>
156
+ <Text>{object.y}</Text>
157
+ <Text>{moment(object.x).format('DD/MM/YYYY HH:mm:ss')}</Text>
158
158
  </View>
159
159
  );
160
160
  })}
@@ -30,8 +30,8 @@ const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
30
30
  setValue(e);
31
31
  };
32
32
  const valueText = useMemo(() => {
33
- return configValues[config.id]?.value || '';
34
- }, [config.id, configValues]);
33
+ return configValues[config?.id]?.value || ''; // config undefined when is_configuration_ready = False
34
+ }, [config?.id, configValues]);
35
35
 
36
36
  return (
37
37
  <View style={(isWidgetOrder && styles.wrapOrderItem) || styles.wrap}>
@@ -7,6 +7,7 @@ import { mockSCStore } from '../../../context/mockStore';
7
7
  import { useConfigGlobalState } from '../../../iot/states';
8
8
  import { Slider } from '@miblanchard/react-native-slider';
9
9
  import _TextInput from '../../Form/TextInput';
10
+ import { DEVICE_TYPE } from '../../../configs/Constants';
10
11
 
11
12
  jest.mock('../../../iot/Monitor');
12
13
  const mockDoAction = jest.fn();
@@ -14,9 +15,14 @@ jest.mock('../../../iot/states', () => ({
14
15
  useConfigGlobalState: jest.fn(),
15
16
  }));
16
17
 
17
- const wrapComponent = (item, doAction, sensor) => (
18
+ const wrapComponent = (item, doAction, sensor, isWidgetOrder = false) => (
18
19
  <SCProvider initState={mockSCStore({})}>
19
- <SliderRangeTemplate item={item} doAction={doAction} sensor={sensor} />
20
+ <SliderRangeTemplate
21
+ item={item}
22
+ doAction={doAction}
23
+ sensor={sensor}
24
+ isWidgetOrder={isWidgetOrder}
25
+ />
20
26
  </SCProvider>
21
27
  );
22
28
 
@@ -37,12 +43,15 @@ describe('Test SliderRangeTemplate', () => {
37
43
  watchMultiConfigs.mockClear();
38
44
  mockDoAction.mockClear();
39
45
  displayItem = {
40
- template: 'ColorPickerTemplate',
46
+ template: 'slider_range_template',
47
+ is_configuration_ready: true,
41
48
  configuration: {
42
49
  config: 5,
43
50
  action_data: actionData,
51
+ min_value: 0,
52
+ max_value: 100,
44
53
  },
45
- title: '',
54
+ label: '',
46
55
  };
47
56
  });
48
57
 
@@ -53,10 +62,16 @@ describe('Test SliderRangeTemplate', () => {
53
62
  wrapper = await create(wrapComponent(displayItem, mockDoAction, sensor));
54
63
  });
55
64
  const instance = wrapper.root;
56
- const silderRange = instance.findAllByType(Slider);
57
- expect(silderRange).toHaveLength(1);
65
+ const sliderRange = instance.findByType(Slider);
66
+ expect(sliderRange.props.value).toBe(0);
58
67
  await act(async () => {
59
- await silderRange[0].props.onSlidingComplete([50]);
68
+ await sliderRange.props.onSlidingStart();
69
+ });
70
+ await act(async () => {
71
+ await sliderRange.props.onValueChange([50]);
72
+ });
73
+ await act(async () => {
74
+ await sliderRange.props.onSlidingComplete([50]);
60
75
  });
61
76
  expect(mockDoAction).toHaveBeenCalledWith(
62
77
  actionData,
@@ -78,8 +93,8 @@ describe('Test SliderRangeTemplate', () => {
78
93
  wrapper = await create(wrapComponent(displayItem, mockDoAction, sensor));
79
94
  });
80
95
  const instance = wrapper.root;
81
- const silderRange = instance.findByType(Slider);
82
- expect(silderRange.props.value).toBe(50);
96
+ const sliderRange = instance.findByType(Slider);
97
+ expect(sliderRange.props.value).toBe(50);
83
98
  });
84
99
 
85
100
  it('test change input invalid number', async () => {
@@ -91,22 +106,24 @@ describe('Test SliderRangeTemplate', () => {
91
106
  },
92
107
  jest.fn(),
93
108
  ]);
94
- const sensor = { is_managed_by_backend: true, name: 'Sensor' };
95
109
 
96
110
  await act(async () => {
97
- wrapper = await create(wrapComponent(displayItem, mockDoAction, sensor));
111
+ wrapper = await create(wrapComponent(displayItem, mockDoAction));
98
112
  });
99
113
  const instance = wrapper.root;
100
114
  const textInput = instance.findByType(_TextInput);
101
115
  expect(textInput.props.value).toEqual('50');
102
116
 
103
117
  await act(async () => {
104
- await textInput.props.onChange('aaaa');
118
+ await textInput.props.onChange('');
119
+ });
120
+ await act(async () => {
121
+ await textInput.props.onEndEditing('');
105
122
  });
106
123
  expect(textInput.props.value).toEqual('50'); // not change value
107
124
  });
108
125
 
109
- it('test change input called doAction', async () => {
126
+ it('test change input text called doAction', async () => {
110
127
  useConfigGlobalState.mockImplementation(() => [
111
128
  {
112
129
  5: {
@@ -115,24 +132,59 @@ describe('Test SliderRangeTemplate', () => {
115
132
  },
116
133
  jest.fn(),
117
134
  ]);
118
- const sensor = { is_managed_by_backend: true, name: 'Sensor' };
119
135
 
120
136
  await act(async () => {
121
- wrapper = await create(wrapComponent(displayItem, mockDoAction, sensor));
137
+ wrapper = await create(wrapComponent(displayItem, mockDoAction));
122
138
  });
123
139
 
124
140
  const instance = wrapper.root;
125
141
  let textInput = instance.findByType(_TextInput);
126
-
142
+ await act(async () => {
143
+ await textInput.props.onFocus();
144
+ });
127
145
  await act(async () => {
128
146
  await textInput.props.onChange('60');
129
147
  });
130
148
  await act(async () => {
131
- jest.runAllTimers();
149
+ await textInput.props.onEndEditing('60');
132
150
  });
133
151
  expect(mockDoAction).toHaveBeenCalledWith(
134
152
  actionData,
135
153
  JSON.stringify({ value_brness: 60, value: 60 })
136
154
  );
137
155
  });
156
+
157
+ it('render template SliderRangeTemplate not ready', async () => {
158
+ useConfigGlobalState.mockImplementation(() => [
159
+ {
160
+ 5: {
161
+ value: 50,
162
+ },
163
+ },
164
+ jest.fn(),
165
+ ]);
166
+ const sensor = { is_managed_by_backend: true, name: 'Sensor' };
167
+ displayItem.is_configuration_ready = false;
168
+ displayItem.configuration = {};
169
+ displayItem.label = undefined;
170
+ await act(async () => {
171
+ wrapper = await create(wrapComponent(displayItem, mockDoAction, sensor));
172
+ });
173
+ const instance = wrapper.root;
174
+ const sliderRange = instance.findByType(Slider);
175
+ expect(sliderRange.props.value).toBe(0);
176
+ });
177
+
178
+ it('render template SliderRangeTemplate sensor device_type LG_THINQ and isWidgetOrder change position', async () => {
179
+ useConfigGlobalState.mockImplementation(() => [{}, jest.fn()]);
180
+ const sensor = { device_type: DEVICE_TYPE.LG_THINQ };
181
+ await act(async () => {
182
+ wrapper = await create(
183
+ wrapComponent(displayItem, mockDoAction, sensor, true)
184
+ );
185
+ });
186
+ const instance = wrapper.root;
187
+ const sliderRange = instance.findByType(Slider);
188
+ expect(sliderRange.props.value).toBe(0);
189
+ });
138
190
  });
@@ -13,7 +13,7 @@ jest.mock('../../../iot/states', () => ({
13
13
 
14
14
  const wrapComponent = (item, mockDoAction, isOn) => (
15
15
  <SCProvider initState={mockSCStore({})}>
16
- <SwitchButtonTemplate item={item} doAction={mockDoAction} isOn={isOn} />
16
+ <SwitchButtonTemplate item={item} doAction={mockDoAction} />
17
17
  </SCProvider>
18
18
  );
19
19
 
@@ -76,7 +76,23 @@ describe('Test SwitchButtonTemplate', () => {
76
76
  expect(switchButton.props.value).toEqual(state);
77
77
  };
78
78
 
79
- it('render state on', async () => {
79
+ const assertActionCall = async (action_data) => {
80
+ const mockDoAction = jest.fn();
81
+ await act(async () => {
82
+ wrapper = await create(wrapComponent(displayItem, mockDoAction));
83
+ });
84
+ expect(mockDoAction).not.toHaveBeenCalled();
85
+ const root = wrapper.root;
86
+
87
+ const item = root.findByType(Switch);
88
+ await act(async () => {
89
+ item.props.onValueChange();
90
+ });
91
+
92
+ expect(mockDoAction).toHaveBeenCalledWith(action_data, null);
93
+ };
94
+
95
+ it('render state on action off', async () => {
80
96
  useConfigGlobalState.mockImplementation(() => [
81
97
  {
82
98
  1: {
@@ -86,9 +102,10 @@ describe('Test SwitchButtonTemplate', () => {
86
102
  jest.fn(),
87
103
  ]);
88
104
  await assertRender(true);
105
+ await assertActionCall(actionOffData);
89
106
  });
90
107
 
91
- it('render state off', async () => {
108
+ it('render state off action on', async () => {
92
109
  useConfigGlobalState.mockImplementation(() => [
93
110
  {
94
111
  1: {
@@ -98,29 +115,20 @@ describe('Test SwitchButtonTemplate', () => {
98
115
  jest.fn(),
99
116
  ]);
100
117
  await assertRender(false);
118
+ await assertActionCall(actionOnData);
101
119
  });
102
120
 
103
- const assertActionCall = async (state, action_data) => {
104
- const mockDoAction = jest.fn();
105
- await act(async () => {
106
- wrapper = await create(wrapComponent(displayItem, mockDoAction, state));
107
- });
108
- expect(mockDoAction).not.toHaveBeenCalled();
109
- const root = wrapper.root;
110
-
111
- const item = root.findByType(Switch);
112
- await act(async () => {
113
- item.props.onValueChange();
114
- });
115
-
116
- expect(mockDoAction).toHaveBeenCalledWith(action_data, null);
117
- };
118
-
119
- it('action state on', async () => {
120
- await assertActionCall(true, actionOffData);
121
- });
122
-
123
- it('action state off', async () => {
124
- await assertActionCall(false, actionOnData);
121
+ it('render with is_on_value 0 action on', async () => {
122
+ displayItem.configuration.is_on_value = [0];
123
+ useConfigGlobalState.mockImplementation(() => [
124
+ {
125
+ 1: {
126
+ value: 1,
127
+ },
128
+ },
129
+ jest.fn(),
130
+ ]);
131
+ await assertRender(false);
132
+ await assertActionCall(actionOnData);
125
133
  });
126
134
  });
@@ -120,6 +120,25 @@ describe('Test TextBoxTemplate', () => {
120
120
  const instance = wrapper.root;
121
121
  const views = instance.findAllByType(View);
122
122
 
123
+ expect(views[0].props.style).toEqual({
124
+ flex: 1,
125
+ flexDirection: 'row',
126
+ justifyContent: 'space-between',
127
+ padding: 16,
128
+ });
129
+ });
130
+ it('render template is_configuration_ready = False', async () => {
131
+ const mockDoAction = jest.fn();
132
+ displayItem = {
133
+ is_configuration_ready: false,
134
+ configuration: {},
135
+ };
136
+ await act(async () => {
137
+ wrapper = await create(wrapComponent(displayItem, mockDoAction, true));
138
+ });
139
+ const instance = wrapper.root;
140
+ const views = instance.findAllByType(View);
141
+
123
142
  expect(views[0].props.style).toEqual({
124
143
  flex: 1,
125
144
  flexDirection: 'row',
@@ -1,4 +1,4 @@
1
- import React, { memo } from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { View, StyleSheet } from 'react-native';
3
3
  import LinearGradient from 'react-native-linear-gradient';
4
4
 
@@ -8,54 +8,62 @@ import Text from '../../../commons/Text';
8
8
  import IconComponent from '../../IconComponent';
9
9
  import images from '../../../configs/Images';
10
10
  import FImage from '../../FImage';
11
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
11
12
 
12
- const CurrentRainSensor = memo(
13
- ({
14
- data = [],
15
- isWidgetOrder,
16
- size = 180,
17
- textType = 'H2',
18
- iconSize = 30,
19
- }) => {
20
- const item = data.length
21
- ? data[0]
22
- : {
23
- evaluate: {},
24
- };
25
- const { text, backgroundColor, borderColor, icon, icon_kit_data } =
26
- item.evaluate || {};
13
+ const CurrentRainSensor = ({
14
+ data = [],
15
+ isWidgetOrder,
16
+ size = 180,
17
+ textType = 'H2',
18
+ iconSize = 30,
19
+ }) => {
20
+ const t = useTranslations();
21
+ const item = useMemo(
22
+ () => (data.length ? data[0] : { evaluate: {} }),
23
+ [data]
24
+ );
27
25
 
26
+ if (!item.evaluate) {
28
27
  return (
29
28
  <View style={[styles.standard, isWidgetOrder && styles.marginBottomZero]}>
30
- <CircleView
31
- size={size}
32
- backgroundColor={backgroundColor}
33
- borderWidth={2}
34
- borderColor={borderColor}
35
- style={styles.center}
36
- >
37
- <LinearGradient
38
- style={styles.linearView}
39
- colors={[Colors.TransparentWhite, 'white']}
40
- start={{ x: 1, y: 0 }}
41
- end={{ x: 1, y: 1 }}
42
- />
43
- {icon_kit_data?.icon || icon ? (
44
- <IconComponent size={iconSize} icon={icon_kit_data?.icon || icon} />
45
- ) : (
46
- <FImage
47
- source={images.activeCurrentSensor}
48
- style={styles.iconDefault}
49
- />
50
- )}
51
- <Text type={textType} style={styles.text}>
52
- {text}
53
- </Text>
54
- </CircleView>
29
+ <Text>{t('widget_have_not_been_shared', { widget: 'Circle' })}</Text>
55
30
  </View>
56
31
  );
57
32
  }
58
- );
33
+
34
+ const { text, backgroundColor, borderColor, icon, icon_kit_data } =
35
+ item.evaluate;
36
+
37
+ return (
38
+ <View style={[styles.standard, isWidgetOrder && styles.marginBottomZero]}>
39
+ <CircleView
40
+ size={size}
41
+ backgroundColor={backgroundColor}
42
+ borderWidth={2}
43
+ borderColor={borderColor}
44
+ style={styles.center}
45
+ >
46
+ <LinearGradient
47
+ style={styles.linearView}
48
+ colors={[Colors.TransparentWhite, 'white']}
49
+ start={{ x: 1, y: 0 }}
50
+ end={{ x: 1, y: 1 }}
51
+ />
52
+ {icon_kit_data?.icon || icon ? (
53
+ <IconComponent size={iconSize} icon={icon_kit_data?.icon || icon} />
54
+ ) : (
55
+ <FImage
56
+ source={images.activeCurrentSensor}
57
+ style={styles.iconDefault}
58
+ />
59
+ )}
60
+ <Text type={textType} style={styles.text}>
61
+ {text}
62
+ </Text>
63
+ </CircleView>
64
+ </View>
65
+ );
66
+ };
59
67
 
60
68
  export default CurrentRainSensor;
61
69
 
@@ -68,9 +76,6 @@ const styles = StyleSheet.create({
68
76
  marginBottomZero: {
69
77
  marginBottom: 0,
70
78
  },
71
- flatlistContent: {
72
- paddingHorizontal: 16,
73
- },
74
79
  center: {
75
80
  alignItems: 'center',
76
81
  justifyContent: 'center',
@@ -30,6 +30,7 @@ const _TextInput = ({
30
30
  selection,
31
31
  onSelectionChange,
32
32
  accessibilityLabelError,
33
+ onEndEditing,
33
34
  }) => {
34
35
  const errorStyle = !!errorText && styles.errorWrap;
35
36
  return (
@@ -69,6 +70,7 @@ const _TextInput = ({
69
70
  {...(value ? { value } : {})}
70
71
  selection={selection}
71
72
  onSelectionChange={onSelectionChange}
73
+ onEndEditing={onEndEditing}
72
74
  />
73
75
  {extraText && extraText}
74
76
  {!!errorText && (
@@ -10,7 +10,7 @@ export default StyleSheet.create({
10
10
  paddingLeft: 7,
11
11
  paddingRight: 10,
12
12
  paddingBottom: 6,
13
- paddingTop: getStatusBarHeight() + 10,
13
+ paddingTop: getStatusBarHeight() + 20,
14
14
  },
15
15
  separator: {
16
16
  borderBottomWidth: 0.5,
@@ -10,7 +10,7 @@ import { AccessibilityLabel } from '../../configs/Constants';
10
10
 
11
11
  const screenHeight = Constants.height;
12
12
  const default_height = 45;
13
- const paddingIos = getStatusBarHeight() + 10;
13
+ const paddingIos = getStatusBarHeight() + 20;
14
14
  export const title_height = 44;
15
15
  export const heightHeader = default_height + title_height + paddingIos;
16
16
 
@@ -126,7 +126,6 @@ const styles = StyleSheet.create({
126
126
  ...Theme.center,
127
127
  },
128
128
  subTitle: {
129
- marginHorizontal: 16,
130
- marginBottom: 16,
129
+ margin: 16,
131
130
  },
132
131
  });
@@ -45,7 +45,7 @@ const SetupScriptDelay = ({ route }) => {
45
45
  }, [automateId, navigate, delay, t]);
46
46
 
47
47
  const canSave = useMemo(() => {
48
- const value = parseInt(delay);
48
+ const value = parseInt(delay, 10);
49
49
  if (!Number.isInteger(value) || value <= 0 || value > 3600) {
50
50
  return false;
51
51
  }
@@ -15,8 +15,13 @@ const InputName = ({ title, placeholder }) => {
15
15
  const t = useTranslations();
16
16
  const { navigate } = useNavigation();
17
17
  const [name, setName] = useState(automate?.name);
18
-
18
+ const [processing, setProcessing] = useState(false);
19
19
  const handleContinue = useCallback(async () => {
20
+ if (processing) {
21
+ /* istanbul ignore next */
22
+ return;
23
+ }
24
+ setProcessing(true);
20
25
  Keyboard.dismiss();
21
26
 
22
27
  const params = {
@@ -37,7 +42,8 @@ const InputName = ({ title, placeholder }) => {
37
42
  },
38
43
  });
39
44
  }
40
- }, [automate, name, navigate, closeScreen]);
45
+ setProcessing(false);
46
+ }, [processing, automate, name, navigate, closeScreen]);
41
47
 
42
48
  return (
43
49
  <NewActionWrapper
@@ -83,7 +83,7 @@ const EditActionsList = memo(() => {
83
83
  paddedIndex,
84
84
  icon,
85
85
  content,
86
- onPressRemove,
86
+ onPress,
87
87
  isActive,
88
88
  drag,
89
89
  }) => (
@@ -103,7 +103,7 @@ const EditActionsList = memo(() => {
103
103
  <View style={styles.contentItem}>{content}</View>
104
104
  <TouchableOpacity
105
105
  accessibilityLabel={AccessibilityLabel.BUTTON_REMOVE_EDIT_ACTION_LIST}
106
- onPress={onPressRemove}
106
+ onPress={onPress}
107
107
  style={styles.closeButton}
108
108
  >
109
109
  <Close />
@@ -148,7 +148,7 @@ const EditActionsList = memo(() => {
148
148
  </Text>
149
149
  </>
150
150
  }
151
- onPressRemove={() => onPressRemove(item)}
151
+ onPress={() => onPressRemove(item)}
152
152
  isActive={isActive}
153
153
  drag={drag}
154
154
  />
@@ -176,7 +176,7 @@ const EditActionsList = memo(() => {
176
176
  </Text>
177
177
  </>
178
178
  }
179
- onPressRemove={() => onPressRemove(item)}
179
+ onPress={() => onPressRemove(item)}
180
180
  isActive={isActive}
181
181
  drag={drag}
182
182
  />
@@ -203,7 +203,7 @@ const EditActionsList = memo(() => {
203
203
  </Text>
204
204
  </>
205
205
  }
206
- onPressRemove={() => onPressRemove(item)}
206
+ onPress={() => onPressRemove(item)}
207
207
  isActive={isActive}
208
208
  drag={drag}
209
209
  />
@@ -38,6 +38,7 @@ describe('test OneTap', () => {
38
38
  unit: { id: 1 },
39
39
  isMultiUnits: false,
40
40
  isAutomateTab: false,
41
+ closeScreen: Routes.ScriptDetail,
41
42
  },
42
43
  };
43
44
 
@@ -66,15 +67,28 @@ describe('test OneTap', () => {
66
67
  await inputName[0].props.onChangeText('Tap to up');
67
68
  });
68
69
 
69
- const item = instance.findAll(
70
+ const item = instance.find(
70
71
  (el) =>
71
72
  el.props.accessibilityLabel === AccessibilityLabel.BOTTOM_VIEW_MAIN &&
72
73
  el.type === TouchableOpacity
73
74
  );
74
75
 
75
- expect(item).toHaveLength(1);
76
76
  await act(async () => {
77
- await item[0].props.onPress();
77
+ await item.props.onPress();
78
+ });
79
+
80
+ expect(mockedNavigate).toHaveBeenCalledWith({
81
+ merge: true,
82
+ name: Routes.ScriptDetail,
83
+ params: {
84
+ newAutomate: {
85
+ id: 1,
86
+ script: { id: 1, name: 'William Miller' },
87
+ type: 'one_tap',
88
+ unit: 1,
89
+ weekday_repeat: [],
90
+ },
91
+ },
78
92
  });
79
93
  });
80
94
 
@@ -22,7 +22,11 @@ import { useEvaluateValue } from '../hooks/useEvaluateValue';
22
22
  import styles from '../styles';
23
23
  import { DetailHistoryChart } from './DetailHistoryChart';
24
24
  import VisualChart from './VisualChart';
25
-
25
+ import { standardizeCameraScreenSize } from '../../../utils/Utils';
26
+ import { Device } from '../../../configs';
27
+ const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
28
+ Device.screenWidth - 32
29
+ );
26
30
  export const SensorDisplayItem = ({
27
31
  item = {},
28
32
  sensor,
@@ -147,8 +151,8 @@ export const SensorDisplayItem = ({
147
151
  }}
148
152
  key={`camera-device-${id}`}
149
153
  cameraName={name}
150
- width="100%"
151
- height="100%"
154
+ width={standardizeWidth}
155
+ height={standardizeHeight}
152
156
  />
153
157
  </View>
154
158
  );
@@ -512,7 +512,7 @@ const DeviceDetail = ({ route }) => {
512
512
  const { configs, options, config, from_config, to_config } =
513
513
  configuration;
514
514
 
515
- configs?.forEach((config) => configIdsSet.add(config.id));
515
+ configs?.forEach((object) => configIdsSet.add(object.id));
516
516
  options?.forEach((option) => configIdsSet.add(option.config));
517
517
 
518
518
  if (config) {
@@ -18,7 +18,7 @@ export default StyleSheet.create({
18
18
  bottom: 0,
19
19
  borderWidth: 0,
20
20
  alignSelf: 'center',
21
- paddingBottom: 16 + getBottomSpace(),
21
+ paddingBottom: 20 + getBottomSpace(),
22
22
  },
23
23
  boxImage: {
24
24
  flexDirection: 'row',
@@ -1464,6 +1464,8 @@ export default {
1464
1464
  end_devices_added: 'end devices added',
1465
1465
  bellow_widget_is_not_configured: 'Bellow widget is not configured',
1466
1466
  bellow_widget_is_wrongly_configured: 'Bellow widget is wrongly configured',
1467
+ widget_have_not_been_shared:
1468
+ '{widget} widget have not been shared. Please contact the owner',
1467
1469
  'customize...': 'Customize...',
1468
1470
  uri_invalid: 'URI invalid',
1469
1471
  when_value_is: 'When value is',
@@ -1474,6 +1474,8 @@ export default {
1474
1474
  end_devices_added: 'thiết bị được thêm vào',
1475
1475
  bellow_widget_is_not_configured: 'Tiện ích bên dưới chưa được cấu hình',
1476
1476
  bellow_widget_is_wrongly_configured: 'Tiện ích bên dưới được cấu hình sai',
1477
+ widget_have_not_been_shared:
1478
+ 'Tiện ích {widget} chưa được chia sẽ. Vui lòng liên hệ với chủ sở hữu',
1477
1479
  'customize...': 'Tùy chỉnh...',
1478
1480
  uri_invalid: 'URI không hợp lệ',
1479
1481
  when_value_is: 'Khi giá trị là',
@@ -1,7 +0,0 @@
1
- import { StyleSheet } from 'react-native';
2
-
3
- export default StyleSheet.create({
4
- wrap: {
5
- marginBottom: 16,
6
- },
7
- });