@eohjsc/react-native-smart-city 0.2.83 → 0.2.84

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.
@@ -0,0 +1,9 @@
1
+ <svg width="22" height="40" viewBox="0 0 22 40" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M21.4995 0H6.49908L0.500977 21.5962H6.51129L1.4009 40H3.20118L20.5983 18.4029H11.785L21.4995 0Z" fill="url(#paint0_linear_8208_3875)"/>
3
+ <defs>
4
+ <linearGradient id="paint0_linear_8208_3875" x1="0.500977" y1="0" x2="33.409" y2="17.2947" gradientUnits="userSpaceOnUse">
5
+ <stop stop-color="#2B6B9F"/>
6
+ <stop offset="1" stop-color="#D5FFCB"/>
7
+ </linearGradient>
8
+ </defs>
9
+ </svg>
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.2.83",
4
+ "version": "0.2.84",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -186,7 +186,7 @@
186
186
  "react-native-udp": "^4.1.3",
187
187
  "react-native-unimodules": "^0.11.0",
188
188
  "react-native-version-check": "^3.4.2",
189
- "react-native-vlc-media-player": "https://github.com/razorRun/react-native-vlc-media-player.git",
189
+ "react-native-vlc-media-player": "^1.0.39",
190
190
  "react-native-webview": "^10.9.3",
191
191
  "react-native-wheel-scrollview-picker": "^1.2.2",
192
192
  "react-native-wifi-reborn": "^4.5.0",
@@ -38,7 +38,7 @@ const ItemAutomate = ({
38
38
  <View style={styles.wrapIcon}>
39
39
  <Icon />
40
40
  </View>
41
- <View>
41
+ <View style={styles.wrapTitle}>
42
42
  <Text type="H4" bold>
43
43
  {t(item?.title)}
44
44
  </Text>
@@ -28,6 +28,10 @@ export default StyleSheet.create({
28
28
  },
29
29
  wrapIcon: {
30
30
  justifyContent: 'center',
31
- marginRight: 16,
31
+ flexDirection: 'row',
32
+ width: 50,
33
+ },
34
+ wrapTitle: {
35
+ marginLeft: 10,
32
36
  },
33
37
  });
@@ -37,7 +37,7 @@ export default ListQualityIndicator;
37
37
  const styles = StyleSheet.create({
38
38
  standard: {
39
39
  flexDirection: 'row',
40
- marginTop: 16,
40
+ marginVertical: 16,
41
41
  },
42
42
  flatlistContent: {
43
43
  paddingHorizontal: 16,
@@ -68,7 +68,7 @@ const styles = StyleSheet.create({
68
68
  borderWidth: 1,
69
69
  borderColor: Colors.Gray4,
70
70
  marginRight: 8,
71
- width: (width / 375) * 120,
71
+ width: ((width - 32) / 375) * 120,
72
72
  },
73
73
  rowFlex: {
74
74
  flexDirection: 'row',
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { View, ActivityIndicator, StyleSheet } from 'react-native';
3
+
4
+ import { colorOpacity } from '../../utils/Converter/color';
5
+ import { Colors } from '../../configs';
6
+
7
+ const FullLoading = ({ wrapStyle, color = Colors.Primary, size = 'small' }) => (
8
+ <View style={[styles.wrap, wrapStyle]}>
9
+ <View style={styles.background}>
10
+ <ActivityIndicator color={color} size={size} />
11
+ </View>
12
+ </View>
13
+ );
14
+
15
+ const styles = StyleSheet.create({
16
+ wrap: {
17
+ position: 'absolute',
18
+ backgroundColor: colorOpacity(Colors.Black, 0.1),
19
+ alignItems: 'center',
20
+ justifyContent: 'center',
21
+ top: 0,
22
+ left: 0,
23
+ bottom: 0,
24
+ right: 0,
25
+ zIndex: 10,
26
+ },
27
+ background: {
28
+ backgroundColor: colorOpacity(Colors.White, 0.8),
29
+ borderRadius: 5,
30
+ paddingHorizontal: 32,
31
+ paddingVertical: 24,
32
+ },
33
+ });
34
+
35
+ export default FullLoading;
@@ -7,6 +7,7 @@ import { API, Colors } from '../../../configs';
7
7
  import OneTap from '../../../../assets/images/OneTap.svg';
8
8
  import ValueChange from '../../../../assets/images/ValueChange.svg';
9
9
  import Schedule from '../../../../assets/images/Schedule.svg';
10
+ import Event from '../../../../assets/images/Event.svg';
10
11
  import CheckCircle from '../../../../assets/images/CheckCircle.svg';
11
12
  import FImage from '../../FImage';
12
13
  import { timeDifference } from '../../../utils/Converter/time';
@@ -92,6 +93,8 @@ const ItemOneTap = memo(
92
93
  return <OneTap />;
93
94
  } else if (type === AUTOMATE_TYPE.VALUE_CHANGE) {
94
95
  return <ValueChange />;
96
+ } else if (type === AUTOMATE_TYPE.EVENT) {
97
+ return <Event />;
95
98
  } else {
96
99
  return <Schedule />;
97
100
  }
@@ -15,6 +15,7 @@ import MenuActionList from './MenuActionList';
15
15
  import RadioCircle from './RadioCircle';
16
16
  import { CircleButton } from './CircleButton';
17
17
  import HorizontalPicker from './HorizontalPicker';
18
+ import FullLoading from './FullLoading';
18
19
 
19
20
  export {
20
21
  ButtonPopup,
@@ -34,4 +35,5 @@ export {
34
35
  RadioCircle,
35
36
  CircleButton,
36
37
  HorizontalPicker,
38
+ FullLoading,
37
39
  };
@@ -25,6 +25,8 @@ const API = {
25
25
  MANAGE_UNIT: (id) => SCConfig.apiRoot + `/property_manager/units/${id}/`,
26
26
  CHIP_SCAN: (id) =>
27
27
  SCConfig.apiRoot + `/property_manager/units/${id}/chip_scan/`,
28
+ CHIP_SCAN_GET_WIFI_INFO: () =>
29
+ SCConfig.apiRoot + '/property_manager/units/chip_scan_get_wifi_info/',
28
30
  MANAGE_ACCESS: (id) =>
29
31
  SCConfig.apiRoot + `/property_manager/manage_access/${id}/`,
30
32
  AUTOMATE: (id) =>
@@ -3,6 +3,7 @@ import { RFValue } from 'react-native-responsive-fontsize';
3
3
  import OneTap from '../../assets/images/OneTap.svg';
4
4
  import ValueChange from '../../assets/images/ValueChange.svg';
5
5
  import Schedule from '../../assets/images/Schedule.svg';
6
+ import Event from '../../assets/images/Event.svg';
6
7
 
7
8
  const X_WIDTH = 375;
8
9
  const X_HEIGHT = 812;
@@ -91,6 +92,7 @@ export const AUTOMATE_TYPE = {
91
92
  ONE_TAP: 'one_tap',
92
93
  VALUE_CHANGE: 'value_change',
93
94
  SCHEDULE: 'schedule',
95
+ EVENT: 'event',
94
96
  ONE_TAP_ONLY: 'one_tap_only',
95
97
  AUTOMATE: 'automate',
96
98
  AUTOMATION: 'automation',
@@ -128,6 +130,12 @@ export const AUTOMATES = {
128
130
  explanation: 'setup_the_schedule',
129
131
  icon: Schedule,
130
132
  },
133
+ event: {
134
+ value: AUTOMATE_TYPE.EVENT,
135
+ title: 'event',
136
+ explanation: 'setup_the_conditions',
137
+ icon: Event,
138
+ },
131
139
  };
132
140
 
133
141
  export const UNIT_TYPES = {
@@ -148,38 +148,56 @@ const SelectAction = memo(({ route }) => {
148
148
  checkedItem,
149
149
  t,
150
150
  ]);
151
-
152
151
  const onSave = useCallback(async () => {
153
152
  if (isSelectSensor) {
154
153
  await checkConditionToContinue();
155
154
  } else {
156
- let list_action = [...actions];
157
- list_action = list_action.map((item) => ({
158
- action: item.action,
159
- data: item.data,
160
- }));
161
- const { success } = await axiosPost(
162
- API.AUTOMATE.ADD_SCRIPT_ACTION(automateId),
163
- {
164
- list_action,
165
- unit: unit.id,
155
+ if (automateId) {
156
+ let list_action = [...actions];
157
+ list_action = list_action.map((item) => ({
158
+ action: item.action,
159
+ data: item.data,
160
+ }));
161
+ const { success } = await axiosPost(
162
+ API.AUTOMATE.ADD_SCRIPT_ACTION(automateId),
163
+ {
164
+ list_action,
165
+ unit: unit.id,
166
+ }
167
+ );
168
+ if (!success) {
169
+ ToastBottomHelper.error(t('not_permission'));
166
170
  }
167
- );
168
- if (!success) {
169
- ToastBottomHelper.error(t('not_permission'));
171
+ navigate(Routes.ScriptDetail, {
172
+ id: automateId,
173
+ name: scriptName,
174
+ havePermission: true,
175
+ unit,
176
+ saveAt: moment().valueOf(),
177
+ type: type,
178
+ isAutomateTab,
179
+ isCreateNewAction,
180
+ isMultiUnits,
181
+ automate,
182
+ });
183
+ } else {
184
+ navigate(Routes.AddNewOneTap, {
185
+ automateData: {
186
+ action: actions[0].action,
187
+ },
188
+ type,
189
+ unit,
190
+ isAutomateTab,
191
+ isSelectSensor,
192
+ isMultiUnits,
193
+ automateId,
194
+ automate: {
195
+ ...automate,
196
+ action: actions[0].action,
197
+ },
198
+ scriptName,
199
+ });
170
200
  }
171
- navigate(Routes.ScriptDetail, {
172
- id: automateId,
173
- name: scriptName,
174
- havePermission: true,
175
- unit,
176
- saveAt: moment().valueOf(),
177
- type: type,
178
- isAutomateTab,
179
- isCreateNewAction,
180
- isMultiUnits,
181
- automate,
182
- });
183
201
  }
184
202
  // eslint-disable-next-line react-hooks/exhaustive-deps
185
203
  }, [
@@ -284,14 +302,14 @@ const SelectAction = memo(({ route }) => {
284
302
  () => (
285
303
  <BottomButtonView
286
304
  style={styles.bottomButtonView}
287
- mainTitle={t(isSelectSensor ? 'continue' : 'save')}
305
+ mainTitle={t(automateId ? 'save' : 'continue')}
288
306
  onPressMain={onSave}
289
307
  typeMain={
290
308
  actions.length > 0 || !!checkedItem?.id ? 'primary' : 'disabled'
291
309
  }
292
310
  />
293
311
  ),
294
- [onSave, actions, checkedItem, isSelectSensor, t]
312
+ [t, automateId, onSave, actions.length, checkedItem?.id]
295
313
  );
296
314
 
297
315
  useEffect(() => {
@@ -3,7 +3,7 @@ import { act, create } from 'react-test-renderer';
3
3
  import AddNewAutoSmart from '..';
4
4
  import { SCProvider } from '../../../context';
5
5
  import { mockSCStore } from '../../../context/mockStore';
6
- import { TESTID } from '../../../configs/Constants';
6
+ import { AUTOMATE_TYPE, TESTID } from '../../../configs/Constants';
7
7
  import ItemAutomate from '../../../commons/Automate/ItemAutomate';
8
8
  import Routes from '../../../utils/Route';
9
9
 
@@ -34,7 +34,7 @@ describe('test AddNewAutoSmart', () => {
34
34
  let tree;
35
35
  let route = {
36
36
  params: {
37
- type: 'value_change',
37
+ type: AUTOMATE_TYPE.VALUE_CHANGE,
38
38
  unit: { id: 1 },
39
39
  },
40
40
  };
@@ -49,7 +49,7 @@ describe('test AddNewAutoSmart', () => {
49
49
  });
50
50
  const instance = tree.root;
51
51
  const items = instance.findAllByType(ItemAutomate);
52
- expect(items).toHaveLength(2);
52
+ expect(items).toHaveLength(3);
53
53
 
54
54
  await act(async () => {
55
55
  await items[0].props.onPress();
@@ -42,6 +42,13 @@ const AddNewAutoSmart = memo(({ route }) => {
42
42
  type: AUTOMATE_TYPE.SCHEDULE,
43
43
  route: Routes.SetSchedule,
44
44
  },
45
+ {
46
+ type: AUTOMATE_TYPE.EVENT,
47
+ route: Routes.SelectSensorDevices,
48
+ data: {
49
+ title: AUTOMATE_SELECT.SELECT_DEVICE,
50
+ },
51
+ },
45
52
  ],
46
53
  [AUTOMATE_TYPE.VALUE_CHANGE]: [
47
54
  {
@@ -55,6 +62,13 @@ const AddNewAutoSmart = memo(({ route }) => {
55
62
  type: AUTOMATE_TYPE.SCHEDULE,
56
63
  route: Routes.SetSchedule,
57
64
  },
65
+ {
66
+ type: AUTOMATE_TYPE.EVENT,
67
+ route: Routes.SelectSensorDevices,
68
+ data: {
69
+ title: AUTOMATE_SELECT.SELECT_DEVICE,
70
+ },
71
+ },
58
72
  ],
59
73
  [AUTOMATE_TYPE.ONE_TAP_ONLY]: [
60
74
  {
@@ -84,7 +98,10 @@ const AddNewAutoSmart = memo(({ route }) => {
84
98
  scriptName,
85
99
  };
86
100
 
87
- if (dataAutomate.type === AUTOMATE_TYPE.VALUE_CHANGE && isMultiUnits) {
101
+ if (
102
+ (dataAutomate.type === AUTOMATE_TYPE.VALUE_CHANGE && isMultiUnits) ||
103
+ (dataAutomate.type === AUTOMATE_TYPE.EVENT && isMultiUnits)
104
+ ) {
88
105
  navigate(Routes.SelectUnit, params);
89
106
  } else {
90
107
  navigate(dataAutomate.route, params);
@@ -60,7 +60,7 @@ const ConnectWifiWarning = memo(({ route }) => {
60
60
  undefined
61
61
  );
62
62
  });
63
- socket.on('message', function (msg, rinfo) {
63
+ socket.on('message', (msg, rinfo) => {
64
64
  const data = JSON.parse(msg.toString());
65
65
  if (data.hasOwnProperty('wifi')) {
66
66
  navigate(Routes.GatewayWifiList, {
@@ -71,27 +71,36 @@ const ConnectWifiWarning = memo(({ route }) => {
71
71
  });
72
72
  }
73
73
  if (data.hasOwnProperty('gateway')) {
74
- navigate(Routes.Dashboard);
75
- ToastBottomHelper.success(t('completed'));
74
+ const checkWifiInterval = setInterval(() => {
75
+ // eslint-disable-next-line promise/prefer-await-to-then
76
+ WifiManager.getCurrentWifiSSID().then(async (ssid) => {
77
+ if (ssid !== wifi_ssid) {
78
+ const { success } = await axiosPost(
79
+ API.UNIT.ADD_GATEWAY(unit_id),
80
+ {
81
+ chip: chip_id,
82
+ imei: data.gateway.imei,
83
+ chip_name: data.gateway.model,
84
+ }
85
+ );
86
+ if (success) {
87
+ navigate(Routes.Dashboard);
88
+ ToastBottomHelper.success(t('add_gateway_success'));
89
+ clearInterval(checkWifiInterval);
90
+ }
91
+ }
92
+ });
93
+ }, 3000);
76
94
  }
77
95
  });
78
96
  socket.on('error', () => {
79
97
  ToastBottomHelper.error(t('server_error'));
80
98
  goBack();
81
99
  });
82
- return () => {
83
- socket.close();
84
- };
85
100
  };
86
101
 
87
102
  const handleConnectWifiGateway = async () => {
88
103
  setIsLoading(true);
89
- const { success } = await axiosPost(API.UNIT.ADD_GATEWAY(unit_id), {
90
- chip: chip_id,
91
- });
92
- if (success) {
93
- ToastBottomHelper.success(t('add_gateway_success'));
94
- }
95
104
  // eslint-disable-next-line promise/prefer-await-to-then
96
105
  WifiManager.connectToProtectedSSID(wifi_ssid, wifi_pass, false).then(
97
106
  () => {
@@ -7,9 +7,6 @@ import { SCProvider } from '../../../context';
7
7
  import { mockSCStore } from '../../../context/mockStore';
8
8
  import SetupGatewayWifi from '../SetupGatewayWifi';
9
9
  import DisplayChecking from '../../../commons/DisplayChecking';
10
- import { AlertAction } from '../../../commons';
11
- import _TextInputPassword from '../../../commons/Form/TextInputPassword';
12
- import _TextInput from '../../../commons/Form/TextInput';
13
10
 
14
11
  const wrapComponent = (route) => (
15
12
  <SCProvider initState={mockSCStore({})}>
@@ -90,53 +87,4 @@ describe('Test SetupGatewayWifi', () => {
90
87
  const displayLoadingConnect = instance.findByType(DisplayChecking);
91
88
  expect(displayLoadingConnect.props.visible).toBeTruthy();
92
89
  });
93
-
94
- test('test render AlertAction', async () => {
95
- await act(async () => {
96
- tree = await create(wrapComponent(route));
97
- });
98
- const instance = tree.root;
99
- const alertAction = instance.findByType(AlertAction);
100
- await act(async () => {
101
- alertAction.props.hideModal();
102
- alertAction.props.leftButtonClick();
103
- });
104
- expect(alertAction).toBeDefined();
105
- });
106
-
107
- test('test render _TextInputPassword', async () => {
108
- await act(async () => {
109
- tree = await create(wrapComponent(route));
110
- });
111
- const instance = tree.root;
112
- const textInputPassword = instance.findByType(_TextInputPassword);
113
- await act(async () => {
114
- textInputPassword.props.onChange('new_name');
115
- });
116
- expect(textInputPassword.props.value).toEqual('new_name');
117
- });
118
-
119
- test('test render TextInput', async () => {
120
- await act(async () => {
121
- tree = await create(wrapComponent(route));
122
- });
123
- const instance = tree.root;
124
- const textInput = instance.findByType(_TextInput);
125
- await act(async () => {
126
- textInput.props.onChange('new_text');
127
- });
128
- expect(textInput.props.value).toEqual('new_text');
129
- });
130
-
131
- test('test render DisplayChecking', async () => {
132
- await act(async () => {
133
- tree = await create(wrapComponent(route));
134
- });
135
- const instance = tree.root;
136
- const displayChecking = instance.findByType(DisplayChecking);
137
- await act(async () => {
138
- displayChecking.props.onClose();
139
- });
140
- expect(displayChecking).toBeDefined();
141
- });
142
90
  });
@@ -70,6 +70,8 @@ export default StyleSheet.create({
70
70
  },
71
71
  simpleList: {
72
72
  height: '100%',
73
+ marginRight: 4,
74
+ marginLeft: 4,
73
75
  },
74
76
  headerRight: {
75
77
  flexDirection: 'row',
@@ -5,8 +5,7 @@ import { axiosPost } from '../../../utils/Apis/axios';
5
5
  import Routes from '../../../utils/Route';
6
6
 
7
7
  const useChipScan = (route) => {
8
- const { unit_id, station, phoneNumber, chipName, wifiName, wifiPass, imei } =
9
- route.params;
8
+ const { unit_id, imei } = route.params;
10
9
  const navigation = useNavigation();
11
10
  const [loading, setLoading] = useState(false);
12
11
 
@@ -15,15 +14,10 @@ const useChipScan = (route) => {
15
14
  setLoading(true);
16
15
  const body = JSON.parse(data);
17
16
  const { success, data: new_chip } = await axiosPost(
18
- API.UNIT.CHIP_SCAN(unit_id),
17
+ API.UNIT.CHIP_SCAN_GET_WIFI_INFO(),
19
18
  {
20
19
  imei: imei || body.imei,
21
20
  qr_code: body.imei,
22
- phone: phoneNumber,
23
- name: chipName,
24
- station: station,
25
- wifi_ssid: wifiName,
26
- wifi_pass: wifiPass,
27
21
  }
28
22
  );
29
23
  if (success) {
@@ -44,17 +38,7 @@ const useChipScan = (route) => {
44
38
  navigation.goBack();
45
39
  }
46
40
  },
47
- [
48
- chipName,
49
- imei,
50
- navigation,
51
- phoneNumber,
52
- route.params,
53
- station,
54
- unit_id,
55
- wifiName,
56
- wifiPass,
57
- ]
41
+ [imei, navigation, route.params, unit_id]
58
42
  );
59
43
 
60
44
  return {
@@ -36,7 +36,7 @@ const ButtonWrapper = ({
36
36
  <TouchableOpacity
37
37
  onPress={onPress}
38
38
  testID={testId}
39
- style={!icon ? styles.buttonWrapper : styles.buttonWrapperAvatar}
39
+ style={styles.buttonWrapper}
40
40
  >
41
41
  <View style={styles.buttonInfo}>
42
42
  {!icon ? (
@@ -16,12 +16,6 @@ export default StyleSheet.create({
16
16
  borderBottomWidth: 0.5,
17
17
  borderBottomColor: Colors.Gray6,
18
18
  },
19
- buttonWrapperAvatar: {
20
- paddingTop: 14,
21
- paddingBottom: 24,
22
- borderBottomWidth: 0.5,
23
- borderBottomColor: Colors.Gray7,
24
- },
25
19
  buttonInfo: {
26
20
  flex: 1,
27
21
  flexDirection: 'row',
@@ -15,7 +15,7 @@ import { useNavigation } from '@react-navigation/native';
15
15
  import { axiosDelete, axiosGet } from '../../utils/Apis/axios';
16
16
  import { SmartAccountItem } from './SmartAccountItem';
17
17
  import { usePopover, useBoolean } from '../../hooks/Common';
18
- import { MenuActionMore, AlertAction } from '../../commons';
18
+ import { MenuActionMore, AlertAction, FullLoading } from '../../commons';
19
19
  import { useTranslations } from '../../hooks/Common/useTranslations';
20
20
  import { useStateAlertRemove } from '../Unit/hook/useStateAlertRemove';
21
21
  import { ToastBottomHelper } from '../../utils/Utils';
@@ -26,6 +26,7 @@ const ListSmartAccount = ({ route }) => {
26
26
  const [data, setData] = useState([]);
27
27
  const smartAccountRef = useRef(null);
28
28
  const { navigate } = useNavigation();
29
+ const [loadingRemoveItem, setLoadingRemoveItem] = useState(false);
29
30
 
30
31
  const getAllSmartAccounts = useCallback(async () => {
31
32
  const { success, data } = await axiosGet(
@@ -70,6 +71,7 @@ const ListSmartAccount = ({ route }) => {
70
71
 
71
72
  const deleteSmartAccount = useCallback(async () => {
72
73
  hideAlertAction();
74
+ setLoadingRemoveItem(true);
73
75
  if (!smartAccountRef?.current) {
74
76
  return;
75
77
  }
@@ -81,6 +83,7 @@ const ListSmartAccount = ({ route }) => {
81
83
  ToastBottomHelper.success(t('removed_successfully'));
82
84
  getAllSmartAccounts();
83
85
  }
86
+ setLoadingRemoveItem(false);
84
87
  }, [getAllSmartAccounts, hideAlertAction, t]);
85
88
 
86
89
  const listMenuItem = useMemo(() => {
@@ -142,6 +145,7 @@ const ListSmartAccount = ({ route }) => {
142
145
  hideComplete={releaseLockShowing}
143
146
  />
144
147
  </WrapHeaderScrollable>
148
+ {loadingRemoveItem && <FullLoading />}
145
149
  </View>
146
150
  );
147
151
  };
@@ -153,7 +153,7 @@ export async function axiosDelete(...options) {
153
153
  }
154
154
  const convertFilenameImage = (filename) => {
155
155
  const filenameConverted = filename?.replace(/HEIC/g, 'jpg');
156
- return filename || filenameConverted;
156
+ return filenameConverted || filename;
157
157
  };
158
158
  export function createFormData(data, list_file_field) {
159
159
  const formData = new FormData();
@@ -186,6 +186,7 @@
186
186
  "explanation": "Do everything with just one button",
187
187
  "value_change": "Value change",
188
188
  "schedule": "Schedule",
189
+ "event": "Event",
189
190
  "setup_the_conditions": "Setup the conditions",
190
191
  "setup_the_schedule": "Setup the Schedule",
191
192
  "des_launch_one_tap": "Quick button create at the dashboard",
@@ -46,6 +46,7 @@
46
46
  "setup_the_conditions": "Cài đặt điều kiện",
47
47
  "setup_the_schedule": "Cài đặt thời gian ",
48
48
  "schedule": "Lịch trình",
49
+ "event": "Sự kiện",
49
50
  "des_launch_one_tap": "Tạo nút nhanh trên trang tổng quan",
50
51
  "active_list": "Danh sách hành động",
51
52
  "filtering": "Đang sục rửa",