@eohjsc/react-native-smart-city 0.4.95 → 0.4.97
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.
- package/package.json +3 -3
- package/src/commons/ActionGroup/OnOffTemplate/index.js +1 -4
- package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -4
- package/src/commons/ActionGroup/TextBoxTemplate.js +68 -0
- package/src/commons/ActionGroup/TextBoxTemplateStyle.js +37 -0
- package/src/commons/ActionGroup/__test__/TextBoxTemplate.test.js +130 -0
- package/src/commons/ActionGroup/index.js +3 -0
- package/src/commons/AlertAction/index.js +2 -0
- package/src/commons/OneTapTemplate/OptionsDropdownActionTemplateStyles.js +0 -3
- package/src/commons/OneTapTemplate/SliderRangeActionTemplate.js +97 -0
- package/src/commons/OneTapTemplate/TextBoxActionTemplate.js +96 -0
- package/src/commons/OneTapTemplate/TextBoxActionTemplateStyles.js +28 -0
- package/src/commons/OneTapTemplate/__test__/SliderRangeActionTemplate.test.js +96 -0
- package/src/commons/OneTapTemplate/__test__/TextBoxActionTemplate.test.js +99 -0
- package/src/configs/AccessibilityLabel.js +5 -1
- package/src/screens/Automate/AddNewAction/ChooseAction.js +20 -0
- package/src/screens/Automate/AddNewAction/NewActionWrapper.js +3 -1
- package/src/screens/Automate/AddNewAction/Styles/SelectActionStyles.js +3 -0
- package/src/screens/ChangePosition/index.js +1 -1
- package/src/screens/Device/components/SensorDisplayItem.js +1 -0
- package/src/utils/I18n/translations/en.js +2 -0
- package/src/utils/I18n/translations/vi.js +2 -0
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.
|
|
4
|
+
"version": "0.4.97",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -196,13 +196,13 @@
|
|
|
196
196
|
"react-native-super-grid": "^4.0.3",
|
|
197
197
|
"react-native-svg": "^12.1.0",
|
|
198
198
|
"react-native-toast-message": "^2.1.1",
|
|
199
|
-
"react-native-udp": "
|
|
199
|
+
"react-native-udp": "4.1.3",
|
|
200
200
|
"react-native-version-check": "^3.4.2",
|
|
201
201
|
"react-native-vlc-media-player": "^1.0.41",
|
|
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",
|
|
205
|
-
"react-native-wifi-reborn": "
|
|
205
|
+
"react-native-wifi-reborn": "4.5.0",
|
|
206
206
|
"react-navigation": "^2.2.0",
|
|
207
207
|
"string-format": "^2.0.0",
|
|
208
208
|
"sync-directory": "^5.1.7",
|
|
@@ -76,10 +76,7 @@ const OnOffTemplate = memo(({ item = {}, doAction, sensor = {} }) => {
|
|
|
76
76
|
config_value: isOn ? 0 : 1,
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
if (device_type === DEVICE_TYPE.LG_THINQ) {
|
|
81
|
-
setTempIsOn((prev) => !prev);
|
|
82
|
-
}
|
|
79
|
+
setTempIsOn((prev) => !prev);
|
|
83
80
|
await doAction(action_data, data);
|
|
84
81
|
updateStatusFromPusher(); // todo Bang read about this magic
|
|
85
82
|
|
|
@@ -91,10 +91,7 @@ const SliderRangeTemplate = memo(
|
|
|
91
91
|
<View
|
|
92
92
|
style={(isWidgetOrder && styles.wrapOrderItem) || styles.viewBrightness}
|
|
93
93
|
>
|
|
94
|
-
<Text type="H4"
|
|
95
|
-
{item?.title || item?.label || t('brightness')}
|
|
96
|
-
</Text>
|
|
97
|
-
|
|
94
|
+
<Text type="H4">{item?.title || item?.label || t('brightness')}</Text>
|
|
98
95
|
<View style={styles.wrap}>
|
|
99
96
|
<Slider
|
|
100
97
|
step={1}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { IconOutline } from '@ant-design/icons-react-native';
|
|
4
|
+
import Text from '../../commons/Text';
|
|
5
|
+
import { AlertAction } from '../../commons';
|
|
6
|
+
import { useDropdownAction } from './hooks/useDropdownAction';
|
|
7
|
+
import { useConfigGlobalState } from '../../iot/states';
|
|
8
|
+
import styles from './TextBoxTemplateStyle';
|
|
9
|
+
|
|
10
|
+
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
11
|
+
import _TextInput from '../Form/TextInput';
|
|
12
|
+
import AccessibilityLabel from '../../configs/AccessibilityLabel';
|
|
13
|
+
|
|
14
|
+
const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
15
|
+
const t = useTranslations();
|
|
16
|
+
const { label, configuration } = item;
|
|
17
|
+
const { action_data, config } = configuration;
|
|
18
|
+
const [configValues] = useConfigGlobalState('configValues');
|
|
19
|
+
const [value, setValue] = useState();
|
|
20
|
+
|
|
21
|
+
const { stateAlert, hideAlertAction, onShowAlert } = useDropdownAction();
|
|
22
|
+
|
|
23
|
+
const onDone = useCallback(() => {
|
|
24
|
+
doAction(action_data, JSON.stringify({ value: value }));
|
|
25
|
+
hideAlertAction();
|
|
26
|
+
}, [doAction, action_data, value, hideAlertAction]);
|
|
27
|
+
|
|
28
|
+
const onInputChange = (value) => {
|
|
29
|
+
setValue(value);
|
|
30
|
+
};
|
|
31
|
+
return (
|
|
32
|
+
<View style={(isWidgetOrder && styles.wrapOrderItem) || styles.wrap}>
|
|
33
|
+
<View>
|
|
34
|
+
<Text type="H4">{label}</Text>
|
|
35
|
+
</View>
|
|
36
|
+
|
|
37
|
+
<View style={styles.iconAndText}>
|
|
38
|
+
<Text type="H4">{configValues[config.id]?.value}</Text>
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
style={styles.iconAndTextOption}
|
|
41
|
+
onPress={onShowAlert}
|
|
42
|
+
accessibilityLabel={AccessibilityLabel.TEXT_BOX_BUTTON_EDIT}
|
|
43
|
+
>
|
|
44
|
+
<IconOutline name="edit" size={20} />
|
|
45
|
+
</TouchableOpacity>
|
|
46
|
+
<AlertAction
|
|
47
|
+
visible={stateAlert.visible}
|
|
48
|
+
hideModal={hideAlertAction}
|
|
49
|
+
title={t('enter_parameters')}
|
|
50
|
+
message={stateAlert.message}
|
|
51
|
+
leftButtonTitle={stateAlert.leftButton}
|
|
52
|
+
leftButtonClick={hideAlertAction}
|
|
53
|
+
rightButtonTitle={stateAlert.rightButton}
|
|
54
|
+
rightButtonClick={onDone}
|
|
55
|
+
rightDisabled={!value}
|
|
56
|
+
>
|
|
57
|
+
<_TextInput
|
|
58
|
+
wrapStyle={styles.wrapInputStyle}
|
|
59
|
+
value={value}
|
|
60
|
+
onChange={onInputChange}
|
|
61
|
+
/>
|
|
62
|
+
</AlertAction>
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default TextBoxTemplate;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors } from '../../configs';
|
|
3
|
+
|
|
4
|
+
export default StyleSheet.create({
|
|
5
|
+
wrapOrderItem: {
|
|
6
|
+
flex: 1,
|
|
7
|
+
flexDirection: 'row',
|
|
8
|
+
justifyContent: 'space-between',
|
|
9
|
+
padding: 16,
|
|
10
|
+
},
|
|
11
|
+
wrap: {
|
|
12
|
+
padding: 16,
|
|
13
|
+
marginHorizontal: 16,
|
|
14
|
+
marginBottom: 16,
|
|
15
|
+
borderWidth: 1,
|
|
16
|
+
borderColor: Colors.Gray4,
|
|
17
|
+
borderRadius: 10,
|
|
18
|
+
flex: 1,
|
|
19
|
+
flexDirection: 'column',
|
|
20
|
+
justifyContent: 'space-between',
|
|
21
|
+
},
|
|
22
|
+
iconAndText: {
|
|
23
|
+
flex: 1,
|
|
24
|
+
flexDirection: 'row',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
},
|
|
27
|
+
iconAndTextOption: {
|
|
28
|
+
flex: 1,
|
|
29
|
+
flexDirection: 'row',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
justifyContent: 'flex-end',
|
|
32
|
+
},
|
|
33
|
+
wrapInputStyle: {
|
|
34
|
+
marginRight: 14,
|
|
35
|
+
paddingLeft: 14,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { act, create } from 'react-test-renderer';
|
|
4
|
+
import { watchMultiConfigs } from '../../../iot/Monitor';
|
|
5
|
+
import { AlertAction } from '../..';
|
|
6
|
+
import { AccessibilityLabel } from '../../../configs/Constants';
|
|
7
|
+
import { SCProvider } from '../../../context';
|
|
8
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
9
|
+
|
|
10
|
+
import TextBoxTemplate from '../TextBoxTemplate';
|
|
11
|
+
import _TextInput from '../../Form/TextInput';
|
|
12
|
+
import { Colors } from '../../../configs';
|
|
13
|
+
|
|
14
|
+
const wrapComponent = (item, mockDoAction, isWidgetOrder = false) => (
|
|
15
|
+
<SCProvider initState={mockSCStore({})}>
|
|
16
|
+
<TextBoxTemplate
|
|
17
|
+
item={item}
|
|
18
|
+
doAction={mockDoAction}
|
|
19
|
+
isWidgetOrder={isWidgetOrder}
|
|
20
|
+
/>
|
|
21
|
+
</SCProvider>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
jest.mock('../../../iot/Monitor');
|
|
25
|
+
|
|
26
|
+
jest.mock('../../../iot/states', () => ({
|
|
27
|
+
useConfigGlobalState: () => [{ 5: { value: 2 } }, null],
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
jest.mock('@react-navigation/native', () => {
|
|
31
|
+
return {
|
|
32
|
+
...jest.requireActual('@react-navigation/native'),
|
|
33
|
+
useFocusEffect: jest.fn(),
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Test OptionsDropdownActionTemplate', () => {
|
|
38
|
+
const actionData = {
|
|
39
|
+
color: '#00979D',
|
|
40
|
+
command_prefer_over_bluetooth: true,
|
|
41
|
+
command_prefer_over_googlehome: false,
|
|
42
|
+
command_prefer_over_internet: false,
|
|
43
|
+
googlehome_actions: [],
|
|
44
|
+
icon: 'caret-up',
|
|
45
|
+
id: 20,
|
|
46
|
+
key: '5ed1d4dc-a905-47cd-b0c9-f979644bd21a',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let displayItem;
|
|
50
|
+
let sensor;
|
|
51
|
+
let wrapper;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
watchMultiConfigs.mockClear();
|
|
55
|
+
displayItem = {
|
|
56
|
+
label: 'label',
|
|
57
|
+
configuration: {
|
|
58
|
+
title: 'Fan Speed',
|
|
59
|
+
action_data: actionData,
|
|
60
|
+
config: { id: 5 },
|
|
61
|
+
icon: 'slack',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
sensor = {
|
|
65
|
+
name: 'Sensor name',
|
|
66
|
+
is_managed_by_backend: false,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('render template', async () => {
|
|
71
|
+
const mockDoAction = jest.fn();
|
|
72
|
+
await act(async () => {
|
|
73
|
+
wrapper = await create(wrapComponent(displayItem, mockDoAction));
|
|
74
|
+
});
|
|
75
|
+
const instance = wrapper.root;
|
|
76
|
+
const views = instance.findAllByType(View);
|
|
77
|
+
|
|
78
|
+
expect(views[0].props.style).toEqual({
|
|
79
|
+
padding: 16,
|
|
80
|
+
marginHorizontal: 16,
|
|
81
|
+
marginBottom: 16,
|
|
82
|
+
borderWidth: 1,
|
|
83
|
+
borderColor: Colors.Gray4,
|
|
84
|
+
borderRadius: 10,
|
|
85
|
+
flex: 1,
|
|
86
|
+
flexDirection: 'column',
|
|
87
|
+
justifyContent: 'space-between',
|
|
88
|
+
});
|
|
89
|
+
const buttonEdit = instance.find(
|
|
90
|
+
(el) =>
|
|
91
|
+
el.props.accessibilityLabel ===
|
|
92
|
+
AccessibilityLabel.TEXT_BOX_BUTTON_EDIT &&
|
|
93
|
+
el.type === TouchableOpacity
|
|
94
|
+
);
|
|
95
|
+
await act(async () => {
|
|
96
|
+
await buttonEdit.props.onPress();
|
|
97
|
+
});
|
|
98
|
+
const alertAction = instance.findByType(AlertAction);
|
|
99
|
+
expect(alertAction.props.visible).toBeTruthy();
|
|
100
|
+
expect(alertAction.props.rightDisabled).toBeTruthy();
|
|
101
|
+
const textInput = instance.findByType(_TextInput);
|
|
102
|
+
await act(async () => {
|
|
103
|
+
await textInput.props.onChange('123');
|
|
104
|
+
});
|
|
105
|
+
expect(alertAction.props.rightDisabled).toBeFalsy();
|
|
106
|
+
await act(async () => {
|
|
107
|
+
await alertAction.props.rightButtonClick();
|
|
108
|
+
});
|
|
109
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
110
|
+
actionData,
|
|
111
|
+
JSON.stringify({ value: '123' })
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
it('render template isWidgetOrder change position', async () => {
|
|
115
|
+
const mockDoAction = jest.fn();
|
|
116
|
+
sensor.is_managed_by_backend = true;
|
|
117
|
+
await act(async () => {
|
|
118
|
+
wrapper = await create(wrapComponent(displayItem, mockDoAction, true));
|
|
119
|
+
});
|
|
120
|
+
const instance = wrapper.root;
|
|
121
|
+
const views = instance.findAllByType(View);
|
|
122
|
+
|
|
123
|
+
expect(views[0].props.style).toEqual({
|
|
124
|
+
flex: 1,
|
|
125
|
+
flexDirection: 'row',
|
|
126
|
+
justifyContent: 'space-between',
|
|
127
|
+
padding: 16,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -13,6 +13,7 @@ import SliderRangeTemplate from './SliderRangeTemplate';
|
|
|
13
13
|
import OnOffSmartLock from './OnOffSmartLock/OnOffSmartLock';
|
|
14
14
|
import TwoButtonTemplate from './TwoButtonTemplate';
|
|
15
15
|
import SwitchButtonTemplate from './OnOffTemplate/SwitchButtonTemplate';
|
|
16
|
+
import TextBoxTemplate from './TextBoxTemplate';
|
|
16
17
|
|
|
17
18
|
export const getActionComponent = (template) => {
|
|
18
19
|
switch (template) {
|
|
@@ -46,6 +47,8 @@ export const getActionComponent = (template) => {
|
|
|
46
47
|
return TwoButtonTemplate;
|
|
47
48
|
case 'switch_button_action_template':
|
|
48
49
|
return SwitchButtonTemplate;
|
|
50
|
+
case 'TextBoxTemplate':
|
|
51
|
+
return TextBoxTemplate;
|
|
49
52
|
default:
|
|
50
53
|
return null;
|
|
51
54
|
}
|
|
@@ -23,6 +23,7 @@ const AlertAction = ({
|
|
|
23
23
|
boxLeftButtonStyle,
|
|
24
24
|
boxRightButtonStyle,
|
|
25
25
|
transY = 0,
|
|
26
|
+
rightDisabled = false,
|
|
26
27
|
}) => {
|
|
27
28
|
const [keyboardAnim] = useState(new Animated.Value(0));
|
|
28
29
|
|
|
@@ -82,6 +83,7 @@ const AlertAction = ({
|
|
|
82
83
|
accessibilityLabelPrefix={accessibilityLabelPrefix}
|
|
83
84
|
wrapStyle={styles.wrapViewButtonStyle}
|
|
84
85
|
disableKeyBoardAnimated
|
|
86
|
+
rightDisabled={rightDisabled}
|
|
85
87
|
/>
|
|
86
88
|
</View>
|
|
87
89
|
</Animated.View>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { memo, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
4
|
+
import styles from './TextBoxActionTemplateStyles';
|
|
5
|
+
import { Colors } from '../../configs';
|
|
6
|
+
import SelectActionCard from '../SelectActionCard';
|
|
7
|
+
import Text from '../Text';
|
|
8
|
+
import { useConfigGlobalState } from '../../iot/states';
|
|
9
|
+
import { AccessibilityLabel } from '../../configs/Constants';
|
|
10
|
+
import { ModalCustom } from '../Modal';
|
|
11
|
+
import _TextInput from '../Form/TextInput';
|
|
12
|
+
|
|
13
|
+
const SliderRangeActionTemplate = ({ device, item, onSelectAction }) => {
|
|
14
|
+
const t = useTranslations();
|
|
15
|
+
const [visible, setVisible] = useState(false);
|
|
16
|
+
const onClose = () => setVisible(false);
|
|
17
|
+
const onPress = () => setVisible(true);
|
|
18
|
+
const { configuration, template, title } = item;
|
|
19
|
+
const { config, action } = configuration;
|
|
20
|
+
const [configValues] = useConfigGlobalState('configValues');
|
|
21
|
+
const [value, setValue] = useState();
|
|
22
|
+
|
|
23
|
+
const onInputChange = (value) => {
|
|
24
|
+
setValue(value);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const configValue = configValues[config.id]?.value;
|
|
29
|
+
setValue(configValue);
|
|
30
|
+
}, [configValues, config]);
|
|
31
|
+
|
|
32
|
+
const onPressDone = useCallback(() => {
|
|
33
|
+
let actionData = { value: value };
|
|
34
|
+
setValue(value);
|
|
35
|
+
onSelectAction &&
|
|
36
|
+
onSelectAction({
|
|
37
|
+
index: item.index,
|
|
38
|
+
action,
|
|
39
|
+
data: actionData,
|
|
40
|
+
template,
|
|
41
|
+
});
|
|
42
|
+
onClose();
|
|
43
|
+
}, [value, onSelectAction, item.index, action, template]);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<SelectActionCard onPress={onPress} action={value} title={title} />
|
|
48
|
+
|
|
49
|
+
<ModalCustom
|
|
50
|
+
isVisible={visible}
|
|
51
|
+
onBackButtonPress={onClose}
|
|
52
|
+
onBackdropPress={onClose}
|
|
53
|
+
>
|
|
54
|
+
<View style={styles.popoverStyle}>
|
|
55
|
+
<View>
|
|
56
|
+
<Text type="H4" bold style={styles.textWithLine}>
|
|
57
|
+
{t('enter_value')}
|
|
58
|
+
</Text>
|
|
59
|
+
<View style={styles.modalContent}>
|
|
60
|
+
<_TextInput
|
|
61
|
+
wrapStyle={styles.wrapInputStyle}
|
|
62
|
+
value={value}
|
|
63
|
+
onChange={onInputChange}
|
|
64
|
+
keyboardType={'numeric'}
|
|
65
|
+
/>
|
|
66
|
+
</View>
|
|
67
|
+
<View style={styles.wrapButton}>
|
|
68
|
+
<TouchableOpacity
|
|
69
|
+
onPress={onClose}
|
|
70
|
+
accessibilityLabel={AccessibilityLabel.BUTTON_CANCEL}
|
|
71
|
+
>
|
|
72
|
+
<Text type="H4" bold color={Colors.Primary}>
|
|
73
|
+
{t('cancel')}
|
|
74
|
+
</Text>
|
|
75
|
+
</TouchableOpacity>
|
|
76
|
+
<TouchableOpacity
|
|
77
|
+
onPress={onPressDone}
|
|
78
|
+
accessibilityLabel={AccessibilityLabel.BUTTON_DONE}
|
|
79
|
+
disabled={!value}
|
|
80
|
+
>
|
|
81
|
+
<Text
|
|
82
|
+
type="H4"
|
|
83
|
+
bold
|
|
84
|
+
color={!value ? Colors.Gray : Colors.Primary}
|
|
85
|
+
>
|
|
86
|
+
{t('done')}
|
|
87
|
+
</Text>
|
|
88
|
+
</TouchableOpacity>
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
</ModalCustom>
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default memo(SliderRangeActionTemplate);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { memo, useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
4
|
+
import styles from './TextBoxActionTemplateStyles';
|
|
5
|
+
import { Colors } from '../../configs';
|
|
6
|
+
import SelectActionCard from '../SelectActionCard';
|
|
7
|
+
import Text from '../Text';
|
|
8
|
+
import { useConfigGlobalState } from '../../iot/states';
|
|
9
|
+
import { AccessibilityLabel } from '../../configs/Constants';
|
|
10
|
+
import { ModalCustom } from '../Modal';
|
|
11
|
+
import _TextInput from '../Form/TextInput';
|
|
12
|
+
|
|
13
|
+
const TextBoxActionTemplate = ({ device, item, onSelectAction }) => {
|
|
14
|
+
const t = useTranslations();
|
|
15
|
+
const [visible, setVisible] = useState(false);
|
|
16
|
+
const onClose = () => setVisible(false);
|
|
17
|
+
const onPress = () => setVisible(true);
|
|
18
|
+
const { configuration, template, title } = item;
|
|
19
|
+
const { config, action } = configuration;
|
|
20
|
+
const [configValues] = useConfigGlobalState('configValues');
|
|
21
|
+
const [value, setValue] = useState();
|
|
22
|
+
|
|
23
|
+
const onInputChange = (value) => {
|
|
24
|
+
setValue(value);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const configValue = configValues[config.id]?.value;
|
|
29
|
+
|
|
30
|
+
setValue(configValue);
|
|
31
|
+
}, [configValues, config]);
|
|
32
|
+
|
|
33
|
+
const onPressDone = useCallback(() => {
|
|
34
|
+
let actionData = { value: value };
|
|
35
|
+
setValue(value);
|
|
36
|
+
onSelectAction &&
|
|
37
|
+
onSelectAction({
|
|
38
|
+
index: item.index,
|
|
39
|
+
action,
|
|
40
|
+
data: actionData,
|
|
41
|
+
template,
|
|
42
|
+
});
|
|
43
|
+
onClose();
|
|
44
|
+
}, [value, onSelectAction, item.index, action, template]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
<SelectActionCard onPress={onPress} action={value} title={title} />
|
|
49
|
+
<ModalCustom
|
|
50
|
+
isVisible={visible}
|
|
51
|
+
onBackButtonPress={onClose}
|
|
52
|
+
onBackdropPress={onClose}
|
|
53
|
+
>
|
|
54
|
+
<View style={styles.popoverStyle}>
|
|
55
|
+
<View>
|
|
56
|
+
<Text type="H4" bold style={styles.textWithLine}>
|
|
57
|
+
{t('enter_parameters')}
|
|
58
|
+
</Text>
|
|
59
|
+
<View style={styles.modalContent}>
|
|
60
|
+
<_TextInput
|
|
61
|
+
wrapStyle={styles.wrapInputStyle}
|
|
62
|
+
value={value}
|
|
63
|
+
onChange={onInputChange}
|
|
64
|
+
/>
|
|
65
|
+
</View>
|
|
66
|
+
<View style={styles.wrapButton}>
|
|
67
|
+
<TouchableOpacity
|
|
68
|
+
onPress={onClose}
|
|
69
|
+
accessibilityLabel={AccessibilityLabel.TEXT_BOX_BUTTON_CANCEL}
|
|
70
|
+
>
|
|
71
|
+
<Text type="H4" bold color={Colors.Primary}>
|
|
72
|
+
{t('cancel')}
|
|
73
|
+
</Text>
|
|
74
|
+
</TouchableOpacity>
|
|
75
|
+
<TouchableOpacity
|
|
76
|
+
onPress={onPressDone}
|
|
77
|
+
accessibilityLabel={AccessibilityLabel.TEXT_BOX_BUTTON_DONE}
|
|
78
|
+
disabled={!value}
|
|
79
|
+
>
|
|
80
|
+
<Text
|
|
81
|
+
type="H4"
|
|
82
|
+
bold
|
|
83
|
+
color={!!value ? Colors.Primary : Colors.Gray}
|
|
84
|
+
>
|
|
85
|
+
{t('done')}
|
|
86
|
+
</Text>
|
|
87
|
+
</TouchableOpacity>
|
|
88
|
+
</View>
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
</ModalCustom>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default memo(TextBoxActionTemplate);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors } from '../../configs';
|
|
3
|
+
|
|
4
|
+
export default StyleSheet.create({
|
|
5
|
+
popoverStyle: {
|
|
6
|
+
width: '100%',
|
|
7
|
+
backgroundColor: Colors.White,
|
|
8
|
+
borderRadius: 10,
|
|
9
|
+
},
|
|
10
|
+
textWithLine: {
|
|
11
|
+
padding: 16,
|
|
12
|
+
},
|
|
13
|
+
modalContent: {
|
|
14
|
+
paddingBottom: 26,
|
|
15
|
+
flexDirection: 'row',
|
|
16
|
+
justifyContent: 'center',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
},
|
|
19
|
+
wrapInputStyle: {
|
|
20
|
+
width: '90%',
|
|
21
|
+
},
|
|
22
|
+
wrapButton: {
|
|
23
|
+
flexDirection: 'row',
|
|
24
|
+
justifyContent: 'space-around',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
marginBottom: 24,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { create, act } from 'react-test-renderer';
|
|
3
|
+
import { SCProvider } from '../../../context';
|
|
4
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
5
|
+
import { TouchableOpacity } from 'react-native';
|
|
6
|
+
import { AccessibilityLabel } from '../../../configs/Constants';
|
|
7
|
+
import SelectActionCard from '../../SelectActionCard';
|
|
8
|
+
import _TextInput from '../../Form/TextInput';
|
|
9
|
+
import SliderRangeActionTemplate from '../SliderRangeActionTemplate';
|
|
10
|
+
|
|
11
|
+
const mockOnSelectAction = jest.fn();
|
|
12
|
+
|
|
13
|
+
const wrapComponent = (item, params = {}) => (
|
|
14
|
+
<SCProvider initState={mockSCStore({})}>
|
|
15
|
+
<SliderRangeActionTemplate
|
|
16
|
+
item={item}
|
|
17
|
+
onSelectAction={mockOnSelectAction}
|
|
18
|
+
{...params}
|
|
19
|
+
/>
|
|
20
|
+
</SCProvider>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('Test SliderRangeActionTemplate', () => {
|
|
24
|
+
let tree;
|
|
25
|
+
let data = {
|
|
26
|
+
title: 'Slider range',
|
|
27
|
+
template: 'slider_range_template',
|
|
28
|
+
configuration: {
|
|
29
|
+
keep_track_config: true,
|
|
30
|
+
config: { id: 1023 },
|
|
31
|
+
action: 'b498234c-6c1a-452d-a1d1-87a314c20528',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
mockOnSelectAction.mockClear();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const renderOptions = async (params = {}) => {
|
|
40
|
+
await act(async () => {
|
|
41
|
+
tree = await create(wrapComponent(data, params));
|
|
42
|
+
});
|
|
43
|
+
const instance = tree.root;
|
|
44
|
+
const card = instance.findByType(SelectActionCard);
|
|
45
|
+
await act(async () => {
|
|
46
|
+
card.props.onPress();
|
|
47
|
+
});
|
|
48
|
+
return instance;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
it('Test onPress Done', async () => {
|
|
52
|
+
const instance = await renderOptions();
|
|
53
|
+
const textInput = instance.findByType(_TextInput);
|
|
54
|
+
await act(async () => {
|
|
55
|
+
await textInput.props.onChange('123');
|
|
56
|
+
});
|
|
57
|
+
const touchableOpacity = instance.find(
|
|
58
|
+
(item) =>
|
|
59
|
+
item.props.accessibilityLabel === AccessibilityLabel.BUTTON_DONE &&
|
|
60
|
+
item.type === TouchableOpacity
|
|
61
|
+
);
|
|
62
|
+
await act(async () => {
|
|
63
|
+
touchableOpacity.props.onPress();
|
|
64
|
+
});
|
|
65
|
+
expect(mockOnSelectAction).toHaveBeenCalledWith({
|
|
66
|
+
action: 'b498234c-6c1a-452d-a1d1-87a314c20528',
|
|
67
|
+
data: { value: '123' },
|
|
68
|
+
index: undefined,
|
|
69
|
+
template: 'slider_range_template',
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('Test onPress Cancel', async () => {
|
|
74
|
+
const instance = await renderOptions();
|
|
75
|
+
const textInput = instance.findByType(_TextInput);
|
|
76
|
+
await act(async () => {
|
|
77
|
+
await textInput.props.onChange('');
|
|
78
|
+
});
|
|
79
|
+
const touchableDone = instance.find(
|
|
80
|
+
(item) =>
|
|
81
|
+
item.props.accessibilityLabel === AccessibilityLabel.BUTTON_DONE &&
|
|
82
|
+
item.type === TouchableOpacity
|
|
83
|
+
);
|
|
84
|
+
expect(touchableDone.props.disabled).toBeTruthy();
|
|
85
|
+
|
|
86
|
+
const touchableOpacity = instance.find(
|
|
87
|
+
(item) =>
|
|
88
|
+
item.props.accessibilityLabel === AccessibilityLabel.BUTTON_CANCEL &&
|
|
89
|
+
item.type === TouchableOpacity
|
|
90
|
+
);
|
|
91
|
+
await act(async () => {
|
|
92
|
+
touchableOpacity.props.onPress();
|
|
93
|
+
});
|
|
94
|
+
expect(mockOnSelectAction).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { create, act } from 'react-test-renderer';
|
|
3
|
+
import { SCProvider } from '../../../context';
|
|
4
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
5
|
+
import { TouchableOpacity } from 'react-native';
|
|
6
|
+
import { AccessibilityLabel } from '../../../configs/Constants';
|
|
7
|
+
import SelectActionCard from '../../SelectActionCard';
|
|
8
|
+
import TextBoxActionTemplate from '../TextBoxActionTemplate';
|
|
9
|
+
import _TextInput from '../../Form/TextInput';
|
|
10
|
+
|
|
11
|
+
const mockOnSelectAction = jest.fn();
|
|
12
|
+
|
|
13
|
+
const wrapComponent = (item, params = {}) => (
|
|
14
|
+
<SCProvider initState={mockSCStore({})}>
|
|
15
|
+
<TextBoxActionTemplate
|
|
16
|
+
item={item}
|
|
17
|
+
onSelectAction={mockOnSelectAction}
|
|
18
|
+
{...params}
|
|
19
|
+
/>
|
|
20
|
+
</SCProvider>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('Test TextBoxActionTemplate', () => {
|
|
24
|
+
let tree;
|
|
25
|
+
let data = {
|
|
26
|
+
title: '',
|
|
27
|
+
template: 'TextBoxTemplate',
|
|
28
|
+
configuration: {
|
|
29
|
+
keep_track_config: true,
|
|
30
|
+
config: { id: 1023 },
|
|
31
|
+
action: 'b498234c-6c1a-452d-a1d1-87a314c20528',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
mockOnSelectAction.mockClear();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const renderOptions = async (params = {}) => {
|
|
40
|
+
await act(async () => {
|
|
41
|
+
tree = await create(wrapComponent(data, params));
|
|
42
|
+
});
|
|
43
|
+
const instance = tree.root;
|
|
44
|
+
const card = instance.findByType(SelectActionCard);
|
|
45
|
+
await act(async () => {
|
|
46
|
+
card.props.onPress();
|
|
47
|
+
});
|
|
48
|
+
return instance;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
it('Test onPress Done', async () => {
|
|
52
|
+
const instance = await renderOptions();
|
|
53
|
+
const textInput = instance.findByType(_TextInput);
|
|
54
|
+
await act(async () => {
|
|
55
|
+
await textInput.props.onChange('123');
|
|
56
|
+
});
|
|
57
|
+
const touchableOpacity = instance.find(
|
|
58
|
+
(item) =>
|
|
59
|
+
item.props.accessibilityLabel ===
|
|
60
|
+
AccessibilityLabel.TEXT_BOX_BUTTON_DONE &&
|
|
61
|
+
item.type === TouchableOpacity
|
|
62
|
+
);
|
|
63
|
+
await act(async () => {
|
|
64
|
+
touchableOpacity.props.onPress();
|
|
65
|
+
});
|
|
66
|
+
expect(mockOnSelectAction).toHaveBeenCalledWith({
|
|
67
|
+
action: 'b498234c-6c1a-452d-a1d1-87a314c20528',
|
|
68
|
+
data: { value: '123' },
|
|
69
|
+
index: undefined,
|
|
70
|
+
template: 'TextBoxTemplate',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('Test onPress Cancel', async () => {
|
|
75
|
+
const instance = await renderOptions();
|
|
76
|
+
const textInput = instance.findByType(_TextInput);
|
|
77
|
+
await act(async () => {
|
|
78
|
+
await textInput.props.onChange('');
|
|
79
|
+
});
|
|
80
|
+
const touchableDone = instance.find(
|
|
81
|
+
(item) =>
|
|
82
|
+
item.props.accessibilityLabel ===
|
|
83
|
+
AccessibilityLabel.TEXT_BOX_BUTTON_DONE &&
|
|
84
|
+
item.type === TouchableOpacity
|
|
85
|
+
);
|
|
86
|
+
expect(touchableDone.props.disabled).toBeTruthy();
|
|
87
|
+
|
|
88
|
+
const touchableOpacity = instance.find(
|
|
89
|
+
(item) =>
|
|
90
|
+
item.props.accessibilityLabel ===
|
|
91
|
+
AccessibilityLabel.TEXT_BOX_BUTTON_CANCEL &&
|
|
92
|
+
item.type === TouchableOpacity
|
|
93
|
+
);
|
|
94
|
+
await act(async () => {
|
|
95
|
+
touchableOpacity.props.onPress();
|
|
96
|
+
});
|
|
97
|
+
expect(mockOnSelectAction).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -497,6 +497,7 @@ export default {
|
|
|
497
497
|
CONNECTED_GATEWAY_UNIT_NAME: 'CONNECTED_GATEWAY_UNIT_NAME',
|
|
498
498
|
CONNECTED_GATEWAY_CHIP_NAME: 'CONNECTED_GATEWAY_CHIP_NAME',
|
|
499
499
|
BUTTON_DONE: 'BUTTON_DONE',
|
|
500
|
+
BUTTON_CANCEL: 'BUTTON_CANCEL',
|
|
500
501
|
|
|
501
502
|
// Add New Device
|
|
502
503
|
ADD_NEW_DEVICE_ADD: 'ADD_NEW_DEVICE_ADD',
|
|
@@ -559,7 +560,10 @@ export default {
|
|
|
559
560
|
NUMBER_UP_DOWN_ACTION_DONE: 'NUMBER_UP_DOWN_ACTION_DONE',
|
|
560
561
|
NUMBER_ACTION_UP: 'NUMBER_ACTION_UP',
|
|
561
562
|
NUMBER_ACTION_DOWN: 'NUMBER_ACTION_DOWN',
|
|
562
|
-
|
|
563
|
+
//TestBoxTemplate
|
|
564
|
+
TEXT_BOX_BUTTON_EDIT: 'TEXT_BOX_BUTTON_EDIT',
|
|
565
|
+
TEXT_BOX_BUTTON_CANCEL: 'TEXT_BOX_BUTTON_CANCEL',
|
|
566
|
+
TEXT_BOX_BUTTON_DONE: 'TEXT_BOX_BUTTON_DONE',
|
|
563
567
|
// OnOffButtonAction
|
|
564
568
|
ON_OFF_BUTTON_ACTION_TITLE: 'ON_OFF_BUTTON_ACTION_TITLE',
|
|
565
569
|
|
|
@@ -13,6 +13,8 @@ import Routes from '../../../utils/Route';
|
|
|
13
13
|
import NewActionWrapper from './NewActionWrapper';
|
|
14
14
|
import moment from 'moment';
|
|
15
15
|
import { ToastBottomHelper } from '../../../utils/Utils';
|
|
16
|
+
import TextBoxActionTemplate from '../../../commons/OneTapTemplate/TextBoxActionTemplate';
|
|
17
|
+
import SliderRangeActionTemplate from '../../../commons/OneTapTemplate/SliderRangeActionTemplate';
|
|
16
18
|
|
|
17
19
|
const RenderActionItem = ({ device, item, handleOnSelectAction, index, t }) => {
|
|
18
20
|
item.index = index;
|
|
@@ -58,6 +60,24 @@ const RenderActionItem = ({ device, item, handleOnSelectAction, index, t }) => {
|
|
|
58
60
|
onSelectAction={handleOnSelectAction}
|
|
59
61
|
/>
|
|
60
62
|
);
|
|
63
|
+
case 'slider_range_template':
|
|
64
|
+
return (
|
|
65
|
+
<SliderRangeActionTemplate
|
|
66
|
+
key={item.id}
|
|
67
|
+
device={device}
|
|
68
|
+
item={item}
|
|
69
|
+
onSelectAction={handleOnSelectAction}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
case 'TextBoxTemplate':
|
|
73
|
+
return (
|
|
74
|
+
<TextBoxActionTemplate
|
|
75
|
+
key={item.id}
|
|
76
|
+
device={device}
|
|
77
|
+
item={item}
|
|
78
|
+
onSelectAction={handleOnSelectAction}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
61
81
|
default:
|
|
62
82
|
ToastBottomHelper.error(
|
|
63
83
|
t('template_not_supported', { template: item.template }),
|
|
@@ -43,7 +43,9 @@ const NewActionWrapper = ({ name, children, canNext, onNext, nextTitle }) => {
|
|
|
43
43
|
isShowSeparator
|
|
44
44
|
wrapTitleStyle={styles.wrapTitleStyle}
|
|
45
45
|
/>
|
|
46
|
-
<KeyboardAwareScrollView
|
|
46
|
+
<KeyboardAwareScrollView contentContainerStyle={styles.scroll}>
|
|
47
|
+
{children}
|
|
48
|
+
</KeyboardAwareScrollView>
|
|
47
49
|
{hasNext && (
|
|
48
50
|
<BottomButtonView
|
|
49
51
|
style={styles.bottomButtonView}
|
|
@@ -170,6 +170,7 @@ export const SensorDisplayItem = ({
|
|
|
170
170
|
case 'slider_range_template':
|
|
171
171
|
case 'two_button_action_template':
|
|
172
172
|
case 'switch_button_action_template':
|
|
173
|
+
case 'TextBoxTemplate':
|
|
173
174
|
return (
|
|
174
175
|
<ActionGroup
|
|
175
176
|
accessibilityLabel={AccessibilityLabel.DEVICE_DETAIL_ACTION_GROUP}
|