@eohjsc/react-native-smart-city 0.3.92 → 0.3.93
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 +5 -1
- package/src/commons/ActionGroup/SliderRangeTemplate.js +2 -2
- package/src/commons/AlertAction/index.js +5 -0
- package/src/commons/BottomButtonView/index.js +22 -4
- package/src/commons/Button/index.js +5 -0
- package/src/commons/Device/ConnectedViewHeader.js +0 -1
- package/src/commons/Device/Emergency/EmergencyDetail.js +4 -2
- package/src/commons/Device/Emergency/__test__/EmergencyDetail.test.js +4 -2
- package/src/commons/Device/ProgressBar/index.js +3 -1
- package/src/commons/Device/ProgressBar/styles.js +1 -4
- package/src/commons/Device/WindSpeed/Anemometer/index.js +1 -1
- package/src/commons/Header/HeaderCustom.js +8 -18
- package/src/commons/SubUnit/OneTap/ItemOneTap.js +5 -48
- package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +6 -15
- package/src/commons/SubUnit/OneTap/index.js +3 -3
- package/src/commons/ViewButtonBottom/index.js +32 -4
- package/src/configs/API.js +3 -0
- package/src/configs/AccessibilityLabel.js +1 -0
- package/src/configs/BLE.js +1 -0
- package/src/context/actionType.ts +2 -1
- package/src/context/mockStore.ts +1 -0
- package/src/context/reducer.ts +12 -1
- package/src/hooks/Explore/useKeyboardAnimated.js +10 -4
- package/src/hooks/IoT/useBluetoothConnection.js +14 -26
- package/src/hooks/useMqtt.js +95 -0
- package/src/iot/Monitor.js +2 -1
- package/src/iot/RemoteControl/Bluetooth.js +56 -19
- package/src/iot/RemoteControl/__test__/Bluetooth.test.js +140 -0
- package/src/iot/mqtt.js +233 -0
- package/src/navigations/UnitStack.js +11 -3
- package/src/screens/AddLocationMaps/index.js +18 -16
- package/src/screens/AddLocationMaps/indexStyle.js +3 -0
- package/src/screens/Automate/AddNewAction/NewActionWrapper.js +3 -6
- package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +29 -29
- package/src/screens/Automate/AddNewAction/__test__/SetupSensor.test.js +45 -39
- package/src/screens/Automate/AddNewAutoSmart/AddAutomationTypeSmart.js +25 -0
- package/src/screens/{AddNewAutoSmart/index.js → Automate/AddNewAutoSmart/AddTypeSmart.js} +16 -51
- package/src/screens/Automate/AddNewAutoSmart/AddUnknownTypeSmart.js +29 -0
- package/src/screens/{AddNewAutoSmart → Automate/AddNewAutoSmart}/__test__/AddNewAutoSmart.test.js +11 -11
- package/src/screens/{AddNewAutoSmart → Automate/AddNewAutoSmart}/styles/AddNewAutoSmartStyles.js +1 -1
- package/src/screens/Automate/Components/InputNameStyles.js +1 -1
- package/src/screens/Automate/EditActionsList/__tests__/index.test.js +11 -25
- package/src/screens/Automate/EditActionsList/index.js +32 -33
- package/src/screens/Automate/MultiUnits.js +1 -1
- package/src/screens/Automate/ScriptDetail/__test__/index.test.js +42 -3
- package/src/screens/Automate/ScriptDetail/index.js +21 -7
- package/src/screens/Automate/__test__/MultiUnits.test.js +1 -1
- package/src/screens/Automate/__test__/index.test.js +1 -1
- package/src/screens/Automate/index.js +1 -1
- package/src/screens/Device/__test__/detail.test.js +1 -1
- package/src/screens/Device/__test__/mqttDetail.test.js +599 -0
- package/src/screens/Device/components/SensorDisplayItem.js +1 -7
- package/src/screens/Device/detail.js +64 -30
- package/src/screens/Device/hooks/useDeviceWatchConfigControl.js +13 -3
- package/src/screens/SelectUnit/__test__/index.test.js +8 -13
- package/src/screens/SmartAccount/__test__/SmartAccount.test.js +8 -4
- package/src/screens/SmartAccount/index.js +8 -9
- package/src/screens/SmartAccount/style.js +8 -7
- package/src/screens/Unit/Detail.js +4 -19
- package/src/screens/Unit/Summaries.js +6 -17
- package/src/screens/Unit/__test__/Summaries.test.js +2 -2
- package/src/utils/FactoryGateway.js +525 -0
- package/src/utils/I18n/translations/en.json +5 -1
- package/src/utils/I18n/translations/vi.json +5 -2
- package/src/utils/Route/index.js +2 -1
- package/src/utils/Utils.js +11 -0
- package/src/commons/Device/SensorConnectedStatus.js +0 -56
- package/src/commons/Device/__test__/SensorConnectedStatus.test.js +0 -29
package/src/iot/mqtt.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { updateGlobalValue } from './Monitor';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
|
|
4
|
+
const int_all = (bytes_str, config_data = null) => {
|
|
5
|
+
let val;
|
|
6
|
+
val = Number.parseInt(bytes_str, 16);
|
|
7
|
+
|
|
8
|
+
if (val > 32767) {
|
|
9
|
+
val = val - 65536;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return val;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const int_first4 = (bytes_str, config_data = null) => {
|
|
16
|
+
return Number.parseInt(bytes_str.slice(0, 4), 16);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const int_last4 = (bytes_str, config_data = null) => {
|
|
20
|
+
return Number.parseInt(bytes_str.slice(4), 16);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const uint_32 = (bytes_str, config_data = null) => {
|
|
24
|
+
let bits = 32;
|
|
25
|
+
let value = Number.parseInt(bytes_str, 16);
|
|
26
|
+
|
|
27
|
+
if (value & (1 << (bits - 1))) {
|
|
28
|
+
value -= 1 << bits;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const float_convert = (bytes_str) => {
|
|
35
|
+
const struct = require('python-struct');
|
|
36
|
+
let value = null;
|
|
37
|
+
try {
|
|
38
|
+
value = struct.unpack('>f', Buffer.from(bytes_str, 'hex'))[0];
|
|
39
|
+
// eslint-disable-next-line no-empty
|
|
40
|
+
} catch {}
|
|
41
|
+
return value;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const float_cdba = (bytes_str, config_data = null) => {
|
|
45
|
+
const bytes_cdba = bytes_str?.slice(4) + bytes_str?.slice(0, 4);
|
|
46
|
+
return float_convert(bytes_cdba);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const float_abcd = (bytes_str, config_data = null) => {
|
|
50
|
+
return float_convert(bytes_str);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const range_value_converting = (
|
|
54
|
+
raw_value,
|
|
55
|
+
raw_min,
|
|
56
|
+
raw_max,
|
|
57
|
+
value_min,
|
|
58
|
+
value_max
|
|
59
|
+
) => {
|
|
60
|
+
const rawValue = parseInt(raw_value, 16);
|
|
61
|
+
const meaning_range = value_max - value_min;
|
|
62
|
+
const value_range = raw_max - raw_min;
|
|
63
|
+
if (!value_range) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const value = rawValue - raw_min;
|
|
68
|
+
const meaning_value = (value * meaning_range) / value_range;
|
|
69
|
+
return meaning_value + value_min;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const convert_ai = (bytes_str, config_data = null) => {
|
|
73
|
+
return range_value_converting(
|
|
74
|
+
bytes_str,
|
|
75
|
+
config_data?.ai_trans_min_raw_in,
|
|
76
|
+
config_data?.ai_trans_max_raw_in,
|
|
77
|
+
config_data?.ai_trans_min_value,
|
|
78
|
+
config_data?.ai_trans_max_value
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const mappingTransformer = {
|
|
83
|
+
int_all: int_all,
|
|
84
|
+
int_first4: int_first4,
|
|
85
|
+
int_last4: int_last4,
|
|
86
|
+
uint_32: uint_32,
|
|
87
|
+
float_cdba: float_cdba,
|
|
88
|
+
float_abcd: float_abcd,
|
|
89
|
+
convert_ai: convert_ai,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const extract_config_value = (register, value_bytes) => {
|
|
93
|
+
const transformer = mappingTransformer[register?.transformer];
|
|
94
|
+
if (!transformer) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return transformer(value_bytes, register);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const get_modbus_length = (config) => {
|
|
102
|
+
const len = config?.len;
|
|
103
|
+
if ([1, 2].includes(config?.func)) {
|
|
104
|
+
return len * 2;
|
|
105
|
+
}
|
|
106
|
+
return len * 4;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const get_config_value = (register, data) => {
|
|
110
|
+
const size = get_modbus_length(register);
|
|
111
|
+
const value_bytes = data?.slice(0, size);
|
|
112
|
+
|
|
113
|
+
const value = extract_config_value(register, value_bytes);
|
|
114
|
+
if (value === undefined || value === null) {
|
|
115
|
+
return { value: null, data: null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { value, data: data.slice(size) };
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const get_sensor_size = (sensor) => {
|
|
122
|
+
let size = 0;
|
|
123
|
+
(sensor?.configs || []).forEach((config) => {
|
|
124
|
+
size += get_modbus_length(config);
|
|
125
|
+
});
|
|
126
|
+
return size;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const get_sensor_data = (sensor, data) => {
|
|
130
|
+
const size = get_sensor_size(sensor);
|
|
131
|
+
return { sensor_data: data.slice(0, size), data: data.slice(size) };
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const get_scale_value = (value, config) => {
|
|
135
|
+
value = value && value * (config?.scale || 1);
|
|
136
|
+
value =
|
|
137
|
+
config?.decimal_behind || config?.decimal_behind === 0
|
|
138
|
+
? value.toFixed(config?.decimal_behind)
|
|
139
|
+
: value;
|
|
140
|
+
return value;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const updateGlobalValueMqtt = (config, value) => {
|
|
144
|
+
if (!config?.id) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (value === undefined || value === null) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
value = get_scale_value(value, config);
|
|
152
|
+
updateGlobalValue(config.id, { value: Number(value) });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const handleModbusData = (chip, configById, data) => {
|
|
156
|
+
const modbus_gateway = chip?.modbus_gateway;
|
|
157
|
+
if (!modbus_gateway) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
(modbus_gateway?.sensors || []).forEach((device) => {
|
|
162
|
+
let { sensor_data, data: new_data } = get_sensor_data(device, data);
|
|
163
|
+
data = new_data;
|
|
164
|
+
|
|
165
|
+
(device?.configs || []).forEach((register) => {
|
|
166
|
+
let { value, data: new_sensor_data } = get_config_value(
|
|
167
|
+
register,
|
|
168
|
+
sensor_data
|
|
169
|
+
);
|
|
170
|
+
sensor_data = new_sensor_data;
|
|
171
|
+
|
|
172
|
+
const configId = register?.config;
|
|
173
|
+
updateGlobalValueMqtt(configById[configId], value);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const handleChipData = (configById, configId, data) => {
|
|
179
|
+
updateGlobalValueMqtt(configById[configId], Number(data?.v));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const handleZigbeeData = (chip, configById, ieee_address, data) => {
|
|
183
|
+
const zigbee_gateway = chip?.zigbee_gateway;
|
|
184
|
+
if (!zigbee_gateway) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const zigbee_device_by_ieee = {};
|
|
189
|
+
|
|
190
|
+
(zigbee_gateway?.sensors || []).forEach((device) => {
|
|
191
|
+
zigbee_device_by_ieee[device.ieee_address] = device;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const device = zigbee_device_by_ieee[ieee_address];
|
|
195
|
+
if (device) {
|
|
196
|
+
(device.configs || []).forEach((config_map) => {
|
|
197
|
+
let value = data?.data[config_map?.key];
|
|
198
|
+
const configId = config_map?.config;
|
|
199
|
+
updateGlobalValueMqtt(configById[configId], value);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const handleMqttMessage = (
|
|
205
|
+
topic,
|
|
206
|
+
payloadData,
|
|
207
|
+
code,
|
|
208
|
+
chip,
|
|
209
|
+
configById
|
|
210
|
+
) => {
|
|
211
|
+
if (topic === `eoh/chip/${code}/data`) {
|
|
212
|
+
// Modbus
|
|
213
|
+
// topic: eoh/chip/${code}/data
|
|
214
|
+
// payload: {data: '01aa010a', 'ack':'1', 'chip_temperature':5333, 'rssi':-61}
|
|
215
|
+
handleModbusData(chip, configById, payloadData.data);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const matchArduinoConfigValue = topic.match(/config\/(\d+)\/value/);
|
|
219
|
+
if (matchArduinoConfigValue) {
|
|
220
|
+
// Arduino
|
|
221
|
+
// topic: eoh/chip/{code}/config/127363/value
|
|
222
|
+
// payload: {v: 2130}
|
|
223
|
+
handleChipData(configById, matchArduinoConfigValue[1], payloadData);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const matchZigbeeConfigValue = topic.match(/zigbee\/(.+)\/data/);
|
|
227
|
+
if (matchZigbeeConfigValue) {
|
|
228
|
+
// Zigbee
|
|
229
|
+
// topic: eoh/chip/code-123/zigbee/IEEE-123/data
|
|
230
|
+
// payload: {zigbee_data: 'aaa', 'data': {state: 1, state_0: 0}}
|
|
231
|
+
handleZigbeeData(chip, configById, matchZigbeeConfigValue[1], payloadData);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
@@ -33,7 +33,7 @@ import TDSGuide from '../screens/TDSGuide';
|
|
|
33
33
|
import WaterQualityGuide from '../screens/WaterQualityGuide';
|
|
34
34
|
import DeviceInfo from '../screens/DeviceInfo';
|
|
35
35
|
import AddNewOneTap from '../screens/Automate/OneTap';
|
|
36
|
-
import
|
|
36
|
+
import AddUnknownTypeSmart from '../screens/Automate/AddNewAutoSmart/AddUnknownTypeSmart';
|
|
37
37
|
import PlaybackCamera from '../screens/PlayBackCamera';
|
|
38
38
|
import AllCamera from '../screens/AllCamera';
|
|
39
39
|
import ManageAccessScreen from '../screens/ManageAccess';
|
|
@@ -65,6 +65,7 @@ import SelectControlDevices from '../screens/Automate/AddNewAction/SelectControl
|
|
|
65
65
|
import ChooseAction from '../screens/Automate/AddNewAction/ChooseAction';
|
|
66
66
|
import ScenarioName from '../screens/Automate/Scenario/ScenarioName';
|
|
67
67
|
import ValueChangeName from '../screens/Automate/ValueChange/ValueChangeName';
|
|
68
|
+
import AddAutomationTypeSmart from '../screens/Automate/AddNewAutoSmart/AddAutomationTypeSmart';
|
|
68
69
|
|
|
69
70
|
const Stack = createStackNavigator();
|
|
70
71
|
|
|
@@ -399,8 +400,15 @@ export const UnitStack = memo((props) => {
|
|
|
399
400
|
}}
|
|
400
401
|
/>
|
|
401
402
|
<Stack.Screen
|
|
402
|
-
name={Route.
|
|
403
|
-
component={
|
|
403
|
+
name={Route.AddUnknownTypeSmart}
|
|
404
|
+
component={AddUnknownTypeSmart}
|
|
405
|
+
options={{
|
|
406
|
+
headerShown: false,
|
|
407
|
+
}}
|
|
408
|
+
/>
|
|
409
|
+
<Stack.Screen
|
|
410
|
+
name={Route.AddAutomationTypeSmart}
|
|
411
|
+
component={AddAutomationTypeSmart}
|
|
404
412
|
options={{
|
|
405
413
|
headerShown: false,
|
|
406
414
|
}}
|
|
@@ -7,7 +7,7 @@ import { check, RESULTS } from 'react-native-permissions';
|
|
|
7
7
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
8
8
|
|
|
9
9
|
import Text from '../../commons/Text';
|
|
10
|
-
import {
|
|
10
|
+
import { FullLoading } from '../../commons';
|
|
11
11
|
import SearchBarLocation from '../../commons/SearchLocation';
|
|
12
12
|
import RowLocation from '../../commons/SearchLocation/RowLocation';
|
|
13
13
|
import { axiosGet } from '../../utils/Apis/axios';
|
|
@@ -25,8 +25,7 @@ import {
|
|
|
25
25
|
OpenSetting,
|
|
26
26
|
} from '../../utils/Permission/common';
|
|
27
27
|
import { openPromptEnableLocation } from '../../utils/Setting/Location';
|
|
28
|
-
|
|
29
|
-
export const initialRadius = 250;
|
|
28
|
+
import BottomButtonView from '../../commons/BottomButtonView';
|
|
30
29
|
|
|
31
30
|
navigator.geolocation = require('@react-native-community/geolocation');
|
|
32
31
|
|
|
@@ -44,11 +43,7 @@ const AddLocationMaps = memo(() => {
|
|
|
44
43
|
location: searchedLocation,
|
|
45
44
|
isAddUnit: true,
|
|
46
45
|
});
|
|
47
|
-
}, [
|
|
48
|
-
|
|
49
|
-
const onBack = useCallback(() => {
|
|
50
|
-
goBack();
|
|
51
|
-
}, [goBack]);
|
|
46
|
+
}, [navigate, searchedLocation]);
|
|
52
47
|
|
|
53
48
|
const onTextInput = useCallback(async (newValue) => {
|
|
54
49
|
setInput(newValue);
|
|
@@ -191,7 +186,10 @@ const AddLocationMaps = memo(() => {
|
|
|
191
186
|
{t('text_explain_add_geolocation')}
|
|
192
187
|
</Text>
|
|
193
188
|
<View style={styles.searchLocation}>
|
|
194
|
-
<SearchBarLocation
|
|
189
|
+
<SearchBarLocation
|
|
190
|
+
input={input?.description || input}
|
|
191
|
+
onTextInput={onTextInput}
|
|
192
|
+
/>
|
|
195
193
|
<ScrollView
|
|
196
194
|
style={styles.searchData}
|
|
197
195
|
scrollIndicatorInsets={{ right: 1 }}
|
|
@@ -282,13 +280,17 @@ const AddLocationMaps = memo(() => {
|
|
|
282
280
|
</MapView>
|
|
283
281
|
</View>
|
|
284
282
|
</View>
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
283
|
+
<BottomButtonView
|
|
284
|
+
rowButton
|
|
285
|
+
style={styles.bottomButton}
|
|
286
|
+
mainTitle={t('done')}
|
|
287
|
+
onPressMain={onDone}
|
|
288
|
+
secondaryTitle={t('cancel')}
|
|
289
|
+
onPressSecondary={goBack}
|
|
290
|
+
typeMain={input?.description ? 'primaryText' : 'disabled'}
|
|
291
|
+
typeSecondary="primaryText"
|
|
292
|
+
accessibilityLabelPrefix="LOCATION_"
|
|
293
|
+
disableBackgroundMainButton
|
|
292
294
|
/>
|
|
293
295
|
{loading && <FullLoading />}
|
|
294
296
|
</View>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useMemo } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import styles from './Styles/SelectActionStyles';
|
|
5
5
|
import WrapHeaderScrollable from '../../../commons/Sharing/WrapHeaderScrollable';
|
|
@@ -32,10 +32,7 @@ const NewActionWrapper = ({ name, children, canNext, onNext, nextTitle }) => {
|
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<
|
|
36
|
-
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
37
|
-
style={styles.wrap}
|
|
38
|
-
>
|
|
35
|
+
<View style={styles.wrap}>
|
|
39
36
|
<WrapHeaderScrollable
|
|
40
37
|
title={name}
|
|
41
38
|
headerAniStyle={styles.headerAniStyle}
|
|
@@ -49,7 +46,7 @@ const NewActionWrapper = ({ name, children, canNext, onNext, nextTitle }) => {
|
|
|
49
46
|
onPressMain={onNext}
|
|
50
47
|
typeMain={canNext ? 'primary' : 'disabled'}
|
|
51
48
|
/>
|
|
52
|
-
</
|
|
49
|
+
</View>
|
|
53
50
|
);
|
|
54
51
|
};
|
|
55
52
|
|
|
@@ -7,11 +7,11 @@ import { Colors, Images } from '../../../configs';
|
|
|
7
7
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
8
8
|
import { ModalCustom } from '../../../commons/Modal';
|
|
9
9
|
import Text from '../../../commons/Text';
|
|
10
|
-
import { HorizontalPicker } from '../../../commons';
|
|
11
10
|
import { setStatusBarPreview } from '../../../hooks/Common/useStatusBar';
|
|
12
11
|
import Routes from '../../../utils/Route';
|
|
13
12
|
import NewActionWrapper from './NewActionWrapper';
|
|
14
13
|
import _TextInput from '../../../commons/Form/TextInput';
|
|
14
|
+
import { ToastBottomHelper } from '../../../utils/Utils';
|
|
15
15
|
|
|
16
16
|
const SetupConfigCondition = () => {
|
|
17
17
|
const t = useTranslations();
|
|
@@ -22,7 +22,7 @@ const SetupConfigCondition = () => {
|
|
|
22
22
|
|
|
23
23
|
const [isShowModal, setIsShowModal] = useState(false);
|
|
24
24
|
const [itemActiveModal, setItemActiveModal] = useState(defaultCondition);
|
|
25
|
-
const [value, setValue] = useState(defaultCondition?.value);
|
|
25
|
+
const [value, setValue] = useState(defaultCondition?.value || '');
|
|
26
26
|
|
|
27
27
|
const hasNoValueEvaluation = !item?.evaluate_configuration;
|
|
28
28
|
|
|
@@ -68,6 +68,27 @@ const SetupConfigCondition = () => {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
const onSave = useCallback(() => {
|
|
71
|
+
const conditionValue = parseFloat(itemActiveModal?.value);
|
|
72
|
+
if (isNaN(conditionValue)) {
|
|
73
|
+
ToastBottomHelper.error(t('value_must_be_a_number'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (['<', '>', '='].includes(itemActiveModal?.condition)) {
|
|
78
|
+
if (item.range_min !== null && conditionValue < item.range_min) {
|
|
79
|
+
ToastBottomHelper.error(
|
|
80
|
+
t('value_must_be_greater_than_min', { min: item.range_min })
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (item.range_max !== null && conditionValue > item.range_max) {
|
|
85
|
+
ToastBottomHelper.error(
|
|
86
|
+
t('value_must_be_less_than_max', { max: item.range_max })
|
|
87
|
+
);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
navigate({
|
|
72
93
|
name: Routes.ChooseConfig,
|
|
73
94
|
merge: true,
|
|
@@ -78,7 +99,7 @@ const SetupConfigCondition = () => {
|
|
|
78
99
|
},
|
|
79
100
|
},
|
|
80
101
|
});
|
|
81
|
-
}, [item.id, itemActiveModal, navigate]);
|
|
102
|
+
}, [item.id, item.range_max, item.range_min, itemActiveModal, navigate, t]);
|
|
82
103
|
|
|
83
104
|
useEffect(() => {
|
|
84
105
|
!hasNoValueEvaluation && setValue(1);
|
|
@@ -98,12 +119,9 @@ const SetupConfigCondition = () => {
|
|
|
98
119
|
);
|
|
99
120
|
}, [isShowModal]);
|
|
100
121
|
|
|
101
|
-
const onChangeValue = useCallback((
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
setValue(newValue);
|
|
105
|
-
setItemActiveModal((prev) => ({ ...prev, value: newValue }));
|
|
106
|
-
}
|
|
122
|
+
const onChangeValue = useCallback((newValue) => {
|
|
123
|
+
setValue(newValue);
|
|
124
|
+
setItemActiveModal((prev) => ({ ...prev, value: newValue }));
|
|
107
125
|
}, []);
|
|
108
126
|
|
|
109
127
|
return (
|
|
@@ -136,7 +154,8 @@ const SetupConfigCondition = () => {
|
|
|
136
154
|
<_TextInput
|
|
137
155
|
textInputStyle={styles.value}
|
|
138
156
|
wrapStyle={styles.wrapValue}
|
|
139
|
-
value={value
|
|
157
|
+
value={value}
|
|
158
|
+
keyboardType={'numeric'}
|
|
140
159
|
onChange={onChangeValue}
|
|
141
160
|
/>
|
|
142
161
|
</View>
|
|
@@ -144,25 +163,6 @@ const SetupConfigCondition = () => {
|
|
|
144
163
|
{item?.unit}
|
|
145
164
|
</Text>
|
|
146
165
|
</View>
|
|
147
|
-
<HorizontalPicker
|
|
148
|
-
minimum={item?.range_min}
|
|
149
|
-
maximum={item?.range_max}
|
|
150
|
-
segmentSpacing={8}
|
|
151
|
-
segmentWidth={8}
|
|
152
|
-
step={
|
|
153
|
-
item?.decimal_behind
|
|
154
|
-
? 1 / Math.pow(10, item?.decimal_behind)
|
|
155
|
-
: 1
|
|
156
|
-
}
|
|
157
|
-
normalHeight={4}
|
|
158
|
-
normalWidth={4}
|
|
159
|
-
stepHeight={12}
|
|
160
|
-
stepWidth={12}
|
|
161
|
-
stepColor={Colors.Gray6}
|
|
162
|
-
normalColor={Colors.Gray6}
|
|
163
|
-
onChangeValue={onChangeValue}
|
|
164
|
-
value={value}
|
|
165
|
-
/>
|
|
166
166
|
</>
|
|
167
167
|
)}
|
|
168
168
|
</NewActionWrapper>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { TouchableOpacity } from 'react-native';
|
|
3
3
|
import { act, create } from 'react-test-renderer';
|
|
4
|
-
import { HorizontalPicker } from '../../../../commons';
|
|
5
4
|
import { SCProvider } from '../../../../context';
|
|
6
5
|
import { mockSCStore } from '../../../../context/mockStore';
|
|
7
6
|
import BottomButtonView from '../../../../commons/BottomButtonView';
|
|
8
7
|
import SetupConfigCondition from '../SetupConfigCondition';
|
|
9
8
|
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
9
|
+
import _TextInput from '../../../../commons/Form/TextInput';
|
|
10
|
+
import { ToastBottomHelper } from '../../../../utils/Utils';
|
|
10
11
|
|
|
11
12
|
jest.mock('react', () => {
|
|
12
13
|
return {
|
|
@@ -24,11 +25,13 @@ const wrapComponent = (configuration, onPress) => (
|
|
|
24
25
|
describe('Test SetupConfigCondition', () => {
|
|
25
26
|
const mockNavigate = useNavigation().navigate;
|
|
26
27
|
const mockGoBack = useNavigation().goBack;
|
|
28
|
+
const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
|
|
27
29
|
|
|
28
30
|
let tree;
|
|
29
31
|
beforeEach(() => {
|
|
30
32
|
mockGoBack.mockReset();
|
|
31
33
|
useRoute.mockClear();
|
|
34
|
+
spyToastError.mockClear();
|
|
32
35
|
});
|
|
33
36
|
|
|
34
37
|
it('Test render', async () => {
|
|
@@ -38,7 +41,7 @@ describe('Test SetupConfigCondition', () => {
|
|
|
38
41
|
id: 1,
|
|
39
42
|
value: 10,
|
|
40
43
|
range_min: 0,
|
|
41
|
-
range_max:
|
|
44
|
+
range_max: 1000,
|
|
42
45
|
decimal_behind: 0,
|
|
43
46
|
title: 'is below (<)',
|
|
44
47
|
sensor_type: 'air_quality',
|
|
@@ -63,10 +66,10 @@ describe('Test SetupConfigCondition', () => {
|
|
|
63
66
|
await act(async () => {
|
|
64
67
|
touchableOpacities[4].props.onPress();
|
|
65
68
|
});
|
|
66
|
-
const
|
|
67
|
-
expect(
|
|
69
|
+
const inputs = instance.findAllByType(_TextInput);
|
|
70
|
+
expect(inputs).toHaveLength(1);
|
|
68
71
|
await act(async () => {
|
|
69
|
-
|
|
72
|
+
inputs[0].props.onChange('128');
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
const BottomButtonViews = instance.findAllByType(BottomButtonView);
|
|
@@ -77,13 +80,13 @@ describe('Test SetupConfigCondition', () => {
|
|
|
77
80
|
expect(mockGoBack).toBeCalled();
|
|
78
81
|
});
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
const testWithRange = async (rangeMin, rangeMax, value, message) => {
|
|
81
84
|
useRoute.mockReturnValue({
|
|
82
85
|
params: {
|
|
83
86
|
item: {
|
|
84
87
|
id: 1,
|
|
85
|
-
range_min:
|
|
86
|
-
range_max:
|
|
88
|
+
range_min: rangeMin,
|
|
89
|
+
range_max: rangeMax,
|
|
87
90
|
decimal_behind: 0,
|
|
88
91
|
title: 'is below (<)',
|
|
89
92
|
sensor_type: 'air_quality',
|
|
@@ -94,38 +97,41 @@ describe('Test SetupConfigCondition', () => {
|
|
|
94
97
|
tree = await create(wrapComponent());
|
|
95
98
|
});
|
|
96
99
|
const instance = tree.root;
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const views = scroll.findByType(View).findAll((el) => {
|
|
100
|
-
return el.type === View && el.props.accessibilityLabel?.includes('x-');
|
|
101
|
-
});
|
|
102
|
-
expect(views).toHaveLength(10 + 1);
|
|
103
|
-
});
|
|
104
|
-
it('Test render when have no maximum and value is near maximum', async () => {
|
|
105
|
-
useRoute.mockReturnValue({
|
|
106
|
-
params: {
|
|
107
|
-
item: {
|
|
108
|
-
id: 1,
|
|
109
|
-
range_min: 0,
|
|
110
|
-
range_max: 0,
|
|
111
|
-
decimal_behind: 1,
|
|
112
|
-
title: 'is below (<)',
|
|
113
|
-
sensor_type: 'air_quality',
|
|
114
|
-
},
|
|
115
|
-
defaultCondition: {
|
|
116
|
-
value: 0.9,
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
});
|
|
100
|
+
const input = instance.findByType(_TextInput);
|
|
101
|
+
|
|
120
102
|
await act(async () => {
|
|
121
|
-
|
|
103
|
+
input.props.onChange(value);
|
|
122
104
|
});
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
|
|
106
|
+
const bottomButtonView = instance.findByType(BottomButtonView);
|
|
107
|
+
|
|
108
|
+
await act(async () => {
|
|
109
|
+
bottomButtonView.props.onPressMain();
|
|
128
110
|
});
|
|
129
|
-
|
|
111
|
+
|
|
112
|
+
if (message === null) {
|
|
113
|
+
expect(mockNavigate).toBeCalled();
|
|
114
|
+
} else {
|
|
115
|
+
expect(spyToastError).toBeCalledWith(message);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
it('Test render when have maximum', async () => {
|
|
120
|
+
await testWithRange(0, 10, '128', 'Value must be less than 10');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('Test render when have no maximum', async () => {
|
|
124
|
+
await testWithRange(0, null, '128', null);
|
|
125
|
+
});
|
|
126
|
+
it('Test render when have minium', async () => {
|
|
127
|
+
await testWithRange(4, 10, '1', 'Value must be greater than 4');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('Test render when have no minium', async () => {
|
|
131
|
+
await testWithRange(null, 10, '1', null);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('Test render when have input not number', async () => {
|
|
135
|
+
await testWithRange(null, 10, 'abc', 'Value must be a number');
|
|
130
136
|
});
|
|
131
137
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AUTOMATE_TYPE } from '../../../configs/Constants';
|
|
3
|
+
import Routes from '../../../utils/Route';
|
|
4
|
+
import AddTypeSmart from './AddTypeSmart';
|
|
5
|
+
|
|
6
|
+
const smartTypes = [
|
|
7
|
+
{
|
|
8
|
+
type: AUTOMATE_TYPE.VALUE_CHANGE,
|
|
9
|
+
route: Routes.SelectMonitorDevices,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
type: AUTOMATE_TYPE.SCHEDULE,
|
|
13
|
+
route: Routes.SetSchedule,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: AUTOMATE_TYPE.EVENT,
|
|
17
|
+
route: Routes.SelectMonitorDevices,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const AddAutomationTypeSmart = ({ route }) => {
|
|
22
|
+
return <AddTypeSmart route={route} smartTypes={smartTypes} />;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default AddAutomationTypeSmart;
|