@eohjsc/react-native-smart-city 0.3.9 → 0.3.10

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 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.3.09",
4
+ "version": "0.3.10",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -96,6 +96,9 @@ const MediaPlayerDetail = ({
96
96
  }, [amount]);
97
97
 
98
98
  const renderCamera = useMemo(() => {
99
+ if (!uri) {
100
+ return null;
101
+ }
99
102
  return (
100
103
  <VLCPlayer
101
104
  autoAspectRatio={true}
@@ -4,8 +4,13 @@ import ConfigHistoryChart from '../';
4
4
  import { SCProvider } from '../../../../context';
5
5
  import { mockSCStore } from '../../../../context/mockStore';
6
6
  import HistoryChart from '../../../../commons/Device/HistoryChart';
7
+ import MockAdapter from 'axios-mock-adapter';
8
+ import { API } from '../../../../configs';
9
+ import api from '../../../../utils/Apis/axios';
7
10
 
8
- const wrapComponent = (configs = []) => (
11
+ const mock = new MockAdapter(api.axiosInstance);
12
+
13
+ const wrapComponent = (configs = [1]) => (
9
14
  <SCProvider initState={mockSCStore({})}>
10
15
  <ConfigHistoryChart configs={configs} />
11
16
  </SCProvider>
@@ -16,6 +21,28 @@ describe('Test HistoryChart', () => {
16
21
 
17
22
  beforeAll(() => {
18
23
  jest.useFakeTimers();
24
+ mock.onGet(API.CONFIG.DISPLAY_HISTORY()).reply(200, [
25
+ {
26
+ config: 1,
27
+ data: [
28
+ {
29
+ url: 'https://valuelog-history/test',
30
+ },
31
+ {
32
+ x: '2022-06-16T00:00:00Z',
33
+ y: 5.0,
34
+ },
35
+ ],
36
+ },
37
+ ]);
38
+ mock.onGet('https://valuelog-history/test').reply(200, [
39
+ {
40
+ config_id: 1,
41
+ start_time: '2022-07-04T02:00:06.151765Z',
42
+ value_log: 0.0,
43
+ converted_value_log: 0.0,
44
+ },
45
+ ]);
19
46
  });
20
47
 
21
48
  it('Test render', async () => {
@@ -11,45 +11,78 @@ export const dateTimeType = {
11
11
  dateTime: 'datetime',
12
12
  };
13
13
 
14
- export const updateConfigChart = (
15
- success,
16
- data,
17
- configuration,
18
- setChartData
19
- ) => {
20
- if (success) {
21
- for (let i = 0; i < data.length; i++) {
22
- for (let j = 0; j < data[i].data.length; j++) {
23
- data[i].data[j].x = moment(data[i].data[j].x).toDate();
24
- }
25
- }
26
- setChartData(
27
- configuration.map((config) => {
28
- config.data = data.find((k) => k.config === config.id).data;
29
- return config;
30
- })
31
- );
32
- }
33
- };
34
-
35
14
  const ConfigHistoryChart = memo(({ configs }) => {
36
15
  const [chartData, setChartData] = useState(configs);
37
16
  const [startDate, setStartDate] = useState(moment().subtract(1, 'days'));
38
17
  const [endDate, setEndDate] = useState(moment());
39
18
 
40
19
  useEffect(() => {
20
+ const fetchDataS3 = (url) => {
21
+ return (
22
+ axiosGet(url, {
23
+ transformRequest: (rq, headers) => {
24
+ delete headers.Authorization;
25
+ return rq;
26
+ },
27
+ })
28
+ // eslint-disable-next-line promise/prefer-await-to-then
29
+ .then((response) => {
30
+ return response.data;
31
+ })
32
+ // eslint-disable-next-line promise/prefer-await-to-callbacks
33
+ .catch((error) => {})
34
+ );
35
+ };
36
+
41
37
  const fetchData = async () => {
42
38
  let params = new URLSearchParams();
43
39
  let configuration = configs.filter((item) => item.id);
44
40
  configuration.map((item) => {
45
41
  params.append('config', item.id);
46
42
  });
47
- params.append('date_from', startDate.valueOf() / 1000);
48
- params.append('date_to', endDate.valueOf() / 1000);
43
+ params.append('date_from', startDate.unix());
44
+ params.append('date_to', endDate.unix());
45
+
49
46
  const { success, data } = await axiosGet(API.CONFIG.DISPLAY_HISTORY(), {
50
47
  params,
51
48
  });
52
- updateConfigChart(success, data, configuration, setChartData);
49
+ if (success) {
50
+ let urls = [];
51
+ data.forEach((config) => {
52
+ config.data.forEach((value_log) => {
53
+ if (value_log.url) {
54
+ urls.push(value_log.url);
55
+ } else {
56
+ value_log.x = moment(value_log.x).toDate();
57
+ }
58
+ });
59
+ });
60
+
61
+ // eslint-disable-next-line promise/prefer-await-to-then
62
+ Promise.all(urls.map(fetchDataS3)).then((result) => {
63
+ let s3_data = [].concat.apply(
64
+ [],
65
+ result.filter((r) => r.length > 0)
66
+ );
67
+ s3_data.forEach((value_log) => {
68
+ value_log.x = moment(value_log.start_time).toDate();
69
+ value_log.y = value_log.converted_value_log;
70
+ });
71
+ setChartData(
72
+ configuration.map((config) => {
73
+ let s3_config_data = [].concat.apply(
74
+ [],
75
+ s3_data.filter((x) => x.config_id === config.id)
76
+ );
77
+ let config_data = data.find((k) => k.config === config.id).data;
78
+ config_data = config_data.filter((k) => !('url' in k));
79
+ config.data = [...s3_config_data, ...config_data];
80
+ config.data = config.data.sort((a, b) => a.x > b.x);
81
+ return config;
82
+ })
83
+ );
84
+ });
85
+ }
53
86
  };
54
87
  fetchData();
55
88
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -77,7 +77,7 @@ const API = {
77
77
  ACCESS: (id) => `/property_manager/shared_sensors/${id}/access/`,
78
78
  },
79
79
  CONFIG: {
80
- DISPLAY_HISTORY: () => '/property_manager/configs/display_history/',
80
+ DISPLAY_HISTORY: () => '/property_manager/configs/display_history_v2/',
81
81
  },
82
82
  AUTOMATE: {
83
83
  ACTION_ONE_TAP: (id) => `/property_manager/automate/${id}/action_one_tap/`,
@@ -60,6 +60,9 @@ export const DEEP_LINK = {
60
60
  NOTIFICATION_SCREEN: 'app://eoh/notifications',
61
61
  };
62
62
 
63
+ const URL_STREAM_CAMERA_DEMO =
64
+ 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
65
+
63
66
  export const Constants = {
64
67
  paddingTop: getStatusBarHeight(),
65
68
  width: Dimensions.get('window').width,
@@ -69,7 +72,7 @@ export const Constants = {
69
72
  FONT_PREFIX,
70
73
  isIphoneX,
71
74
  DEEP_LINK,
72
- URL_STREAM_BALCONY_CAMERA,
75
+ URL_STREAM_CAMERA_DEMO,
73
76
  };
74
77
 
75
78
  export const DEVICE_TYPE = {
@@ -144,6 +147,25 @@ export const CONDITION_TYPES = {
144
147
  IS_ABOVE: 'IS_ABOVE',
145
148
  };
146
149
 
150
+ export const STATE_VALUE_SENSOR_TYPES = [
151
+ {
152
+ type: 'smoke',
153
+ stateValue: ['not_detected', 'detected'],
154
+ },
155
+ {
156
+ type: 'fire',
157
+ stateValue: ['not_detected', 'detected'],
158
+ },
159
+ {
160
+ type: 'sos',
161
+ stateValue: ['not_activated', 'activated'],
162
+ },
163
+ {
164
+ type: 'filter_water',
165
+ stateValue: ['not_detected', 'detected'],
166
+ },
167
+ ];
168
+
147
169
  export const AUTOMATES = {
148
170
  one_tap: {
149
171
  value: AUTOMATE_TYPE.ONE_TAP,
@@ -830,6 +852,3 @@ export const PROBLEM_CODE = {
830
852
  UNKNOWN_ERROR: 'UNKNOWN_ERROR',
831
853
  CANCEL_ERROR: 'CANCEL_ERROR',
832
854
  };
833
-
834
- const URL_STREAM_BALCONY_CAMERA =
835
- 'rtsp://admin:Eoh@2020@101.99.33.220:30554/main';
@@ -80,6 +80,7 @@ export type AppType = {
80
80
 
81
81
  export type IoTType = {
82
82
  googlehome: {
83
+ isFirstTimeConnect: boolean;
83
84
  isConnecting: boolean;
84
85
  connections: {};
85
86
  };
@@ -32,6 +32,7 @@ export const mockDataStore: ContextData = {
32
32
  starredScriptIds: [],
33
33
  },
34
34
  iot: {
35
+ isFirstTimeConnect: true,
35
36
  isConnecting: false,
36
37
  googlehome: {},
37
38
  },
@@ -60,6 +60,8 @@ export const initialState = {
60
60
  },
61
61
  iot: {
62
62
  googlehome: {
63
+ isFirstTimeConnect: true,
64
+ isConnecting: false,
63
65
  connections: {},
64
66
  },
65
67
  },
@@ -256,6 +258,7 @@ export const reducer = (currentState: ContextData, action: Action) => {
256
258
  ...currentState.iot,
257
259
  googlehome: {
258
260
  ...currentState.iot.googlehome,
261
+ isFirstTimeConnect: false,
259
262
  isConnecting: false,
260
263
  connections: payload,
261
264
  },
@@ -268,6 +271,7 @@ export const reducer = (currentState: ContextData, action: Action) => {
268
271
  ...currentState.iot,
269
272
  googlehome: {
270
273
  ...currentState.iot.googlehome,
274
+ isFirstTimeConnect: false,
271
275
  isConnecting: false,
272
276
  connections: {
273
277
  ...currentState.iot.googlehome.connections,
@@ -283,7 +287,9 @@ export const reducer = (currentState: ContextData, action: Action) => {
283
287
  ...currentState.iot,
284
288
  googlehome: {
285
289
  ...currentState.iot.googlehome,
286
- isConnecting: true,
290
+ isConnecting: currentState.iot.googlehome.isFirstTimeConnect
291
+ ? true
292
+ : false,
287
293
  },
288
294
  },
289
295
  };
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState, useCallback } from 'react';
2
2
  import OneSignal from 'react-native-onesignal';
3
3
 
4
- const useReceiveNotifications = () => {
4
+ const useReceiveNotifications = (callBack) => {
5
5
  const [dataNofitication, setDataNofitication] = useState(null);
6
6
 
7
7
  const onReceived = useCallback((data) => {
@@ -15,9 +15,10 @@ const useReceiveNotifications = () => {
15
15
  let notif = notifReceivedEvent.getNotification();
16
16
  setTimeout(() => notifReceivedEvent.complete(notif), 0);
17
17
  onReceived(notif);
18
+ callBack && callBack();
18
19
  }
19
20
  );
20
- }, [onReceived]);
21
+ }, [callBack, onReceived]);
21
22
 
22
23
  return { dataNofitication };
23
24
  };
@@ -0,0 +1,265 @@
1
+ import React from 'react';
2
+ import { Alert } from 'react-native';
3
+ import { act, create } from 'react-test-renderer';
4
+ import MockAdapter from 'axios-mock-adapter';
5
+ import RNP from 'react-native-permissions';
6
+ import RNAndroidLocationEnabler from 'react-native-android-location-enabler';
7
+
8
+ import { SCProvider } from '../../../context';
9
+ import { mockSCStore } from '../../../context/mockStore';
10
+ import AddLocationMaps from '../index';
11
+ import { API } from '../../../configs';
12
+ import { TESTID } from '../../../configs/Constants';
13
+ import api from '../../../utils/Apis/axios';
14
+ import {
15
+ GEOLOCATION_ERROR,
16
+ OpenSetting,
17
+ } from '../../../utils/Permission/common';
18
+ import { RESULTS } from 'react-native-permissions';
19
+
20
+ const wrapComponent = (route) => (
21
+ <SCProvider initState={mockSCStore({})}>
22
+ <AddLocationMaps route={route} />
23
+ </SCProvider>
24
+ );
25
+
26
+ const mock = new MockAdapter(api.axiosInstance);
27
+
28
+ const mockNavigate = jest.fn();
29
+ const mockGoBack = jest.fn();
30
+ jest.mock('@react-navigation/native', () => {
31
+ return {
32
+ ...jest.requireActual('@react-navigation/native'),
33
+ useNavigation: () => ({
34
+ navigate: mockNavigate,
35
+ goBack: mockGoBack,
36
+ }),
37
+ };
38
+ });
39
+
40
+ jest.mock('react', () => {
41
+ return {
42
+ ...jest.requireActual('react'),
43
+ memo: (x) => x,
44
+ };
45
+ });
46
+
47
+ jest.mock('react-native-maps', () => {
48
+ const { forwardRef } = require('react');
49
+ const { View } = require('react-native');
50
+ const MockMapView = forwardRef((props, ref) => (
51
+ <View refs={ref}>{props.children}</View>
52
+ ));
53
+ const MockMarker = (props) => <View>{props.children}</View>;
54
+ const MockCircle = (props) => <View>{props.children}</View>;
55
+ return {
56
+ __esModule: true,
57
+ default: MockMapView,
58
+ Marker: MockMarker,
59
+ Circle: MockCircle,
60
+ PROVIDER_GOOGLE: 'google',
61
+ };
62
+ });
63
+
64
+ jest.mock('react-native-android-location-enabler', () => {
65
+ return {
66
+ ...jest.requireActual('react-native-android-location-enabler'),
67
+ promptForEnableLocationIfNeeded: jest.fn(),
68
+ };
69
+ });
70
+
71
+ jest.mock('../../../utils/Permission/common');
72
+
73
+ const position = {
74
+ coords: {
75
+ latitude: 100,
76
+ longitude: 100,
77
+ },
78
+ };
79
+ const mockGeolocation = {
80
+ getCurrentPosition: (onSuccess, onError, options) => {
81
+ onSuccess(position);
82
+ },
83
+ };
84
+ global.navigator.geolocation = mockGeolocation;
85
+
86
+ jest.mock('react-native-permissions', () => {
87
+ return require('react-native-permissions/mock');
88
+ });
89
+
90
+ describe('Test SelectAddress', () => {
91
+ let tree, route, Platform;
92
+ const mockUpdateLocation = jest.fn();
93
+
94
+ beforeAll(() => {
95
+ Platform = require('react-native').Platform;
96
+ RNAndroidLocationEnabler.promptForEnableLocationIfNeeded.mockClear();
97
+ RNP.check.mockClear();
98
+ route = {
99
+ params: {
100
+ updateLocation: mockUpdateLocation,
101
+ },
102
+ };
103
+ });
104
+
105
+ test('test get current location success', async () => {
106
+ await act(async () => {
107
+ tree = await create(wrapComponent(route));
108
+ });
109
+ const instance = tree.root;
110
+ const button = instance.find(
111
+ (el) => el.props.testID === TESTID.BUTTON_YOUR_LOCATION
112
+ );
113
+
114
+ const response = {
115
+ status: 200,
116
+ data: {
117
+ results: [
118
+ {
119
+ formatted_address: 'address',
120
+ geometry: {
121
+ location: {
122
+ lat: 10,
123
+ lng: 10,
124
+ },
125
+ },
126
+ },
127
+ ],
128
+ },
129
+ };
130
+ mock
131
+ .onGet(API.EXTERNAL.GOOGLE_MAP.GET_LOCATION_FROM_LAT_LNG)
132
+ .reply(200, response.data);
133
+ await act(async () => {
134
+ await button.props.onPress();
135
+ });
136
+ });
137
+
138
+ test('test get current location failed permission denied', async () => {
139
+ await act(async () => {
140
+ tree = await create(wrapComponent(route));
141
+ });
142
+ const instance = tree.root;
143
+ const button = instance.find(
144
+ (el) => el.props.testID === TESTID.BUTTON_YOUR_LOCATION
145
+ );
146
+
147
+ global.navigator.geolocation = {
148
+ getCurrentPosition: (onSuccess, onError, options) => {
149
+ onError({ code: GEOLOCATION_ERROR.PERMISSION_DENIED });
150
+ },
151
+ };
152
+ RNP.check.mockImplementationOnce(() => RESULTS.BLOCKED);
153
+ OpenSetting.mockImplementationOnce(() => {});
154
+
155
+ await act(async () => {
156
+ await button.props.onPress();
157
+ });
158
+ OpenSetting.mockClear();
159
+ });
160
+
161
+ test('test get current location failed location not enabled android', async () => {
162
+ Platform.OS = 'android';
163
+ await act(async () => {
164
+ tree = await create(wrapComponent(route));
165
+ });
166
+ const instance = tree.root;
167
+ const button = instance.find(
168
+ (el) => el.props.testID === TESTID.BUTTON_YOUR_LOCATION
169
+ );
170
+
171
+ global.navigator.geolocation = {
172
+ getCurrentPosition: (onSuccess, onError, options) => {
173
+ onError({ code: GEOLOCATION_ERROR.POSITION_UNAVAILABLE });
174
+ },
175
+ };
176
+
177
+ // cancel
178
+ RNAndroidLocationEnabler.promptForEnableLocationIfNeeded.mockImplementationOnce(
179
+ async () => {
180
+ throw new Error();
181
+ }
182
+ );
183
+ await act(async () => {
184
+ await button.props.onPress();
185
+ });
186
+ expect(
187
+ RNAndroidLocationEnabler.promptForEnableLocationIfNeeded
188
+ ).toBeCalled();
189
+
190
+ RNAndroidLocationEnabler.promptForEnableLocationIfNeeded.mockClear();
191
+
192
+ // enabled
193
+ RNAndroidLocationEnabler.promptForEnableLocationIfNeeded.mockImplementationOnce(
194
+ async () => true
195
+ );
196
+ await act(async () => {
197
+ await button.props.onPress();
198
+ });
199
+ expect(
200
+ RNAndroidLocationEnabler.promptForEnableLocationIfNeeded
201
+ ).toBeCalled();
202
+ });
203
+
204
+ test('test get current location failed location not enabled ios', async () => {
205
+ Platform.OS = 'ios';
206
+ jest.spyOn(Alert, 'alert');
207
+
208
+ await act(async () => {
209
+ tree = await create(wrapComponent(route));
210
+ });
211
+ const instance = tree.root;
212
+ const button = instance.find(
213
+ (el) => el.props.testID === TESTID.BUTTON_YOUR_LOCATION
214
+ );
215
+
216
+ global.navigator.geolocation = {
217
+ getCurrentPosition: (onSuccess, onError, options) => {
218
+ onError({ code: GEOLOCATION_ERROR.POSITION_UNAVAILABLE });
219
+ },
220
+ };
221
+
222
+ await act(async () => {
223
+ await button.props.onPress();
224
+ });
225
+ // expect AlertAsync is called
226
+ });
227
+
228
+ test('test get current location failed error not handle', async () => {
229
+ await act(async () => {
230
+ tree = await create(wrapComponent(route));
231
+ });
232
+ const instance = tree.root;
233
+ const button = instance.find(
234
+ (el) => el.props.testID === TESTID.BUTTON_YOUR_LOCATION
235
+ );
236
+
237
+ global.navigator.geolocation = {
238
+ getCurrentPosition: (onSuccess, onError, options) => {
239
+ onError({ code: GEOLOCATION_ERROR.TIMEOUT });
240
+ },
241
+ };
242
+ RNP.check.mockImplementationOnce(() => RESULTS.DENIED);
243
+ OpenSetting.mockImplementationOnce(() => {});
244
+
245
+ await act(async () => {
246
+ await button.props.onPress();
247
+ });
248
+ expect(OpenSetting).toBeCalledTimes(0);
249
+ OpenSetting.mockClear();
250
+ });
251
+
252
+ test('test choose on map', async () => {
253
+ await act(async () => {
254
+ tree = await create(wrapComponent(route));
255
+ });
256
+ const instance = tree.root;
257
+ const button = instance.find(
258
+ (el) => el.props.testID === TESTID.BUTTON_CHOOSE_ON_MAP
259
+ );
260
+ await act(async () => {
261
+ await button.props.onPress();
262
+ });
263
+ expect(mockNavigate).toBeCalled();
264
+ });
265
+ });
@@ -3,26 +3,30 @@ import { View, ScrollView, TouchableOpacity } from 'react-native';
3
3
  import MapView, { Marker, Circle, PROVIDER_GOOGLE } from 'react-native-maps';
4
4
  import { useNavigation } from '@react-navigation/native';
5
5
  import { IconOutline, IconFill } from '@ant-design/icons-react-native';
6
+ import { check, RESULTS } from 'react-native-permissions';
6
7
  import { useTranslations } from '../../hooks/Common/useTranslations';
7
8
 
8
9
  import Text from '../../commons/Text';
9
- import { ViewButtonBottom } from '../../commons';
10
+ import { ViewButtonBottom, FullLoading } from '../../commons';
10
11
  import SearchBarLocation from '../../commons/SearchLocation';
11
12
  import RowLocation from '../../commons/SearchLocation/RowLocation';
12
13
  import { axiosGet } from '../../utils/Apis/axios';
13
14
  import { API, Colors, SCConfig } from '../../configs';
14
- import { TESTID } from '../../configs/Constants';
15
+ import {
16
+ TESTID,
17
+ MAP_INITIAL_REGION,
18
+ EOH_LOCATION,
19
+ } from '../../configs/Constants';
15
20
  import styles from './indexStyle';
16
21
  import Routes from '../../utils/Route';
22
+ import {
23
+ GEOLOCATION_ERROR,
24
+ keyPermission,
25
+ OpenSetting,
26
+ } from '../../utils/Permission/common';
27
+ import { openPromptEnableLocation } from '../../utils/Setting/Location';
17
28
 
18
29
  export const initialRadius = 250;
19
- const initialRegion = {
20
- latitudeDelta: 0.0090222,
21
- longitudeDelta: 0.009221,
22
- };
23
-
24
- const DEFAULT_LATITUDE = 10.7974046; // EoH center
25
- const DEFAULT_LONGITUDE = 106.7035663;
26
30
 
27
31
  navigator.geolocation = require('@react-native-community/geolocation');
28
32
 
@@ -32,7 +36,7 @@ const AddLocationMaps = memo(() => {
32
36
  const [input, setInput] = useState('');
33
37
  const [searchData, setSearchData] = useState([]);
34
38
  const [searchedLocation, setSearchedLocation] = useState(null);
35
-
39
+ const [loading, setLoading] = useState(false);
36
40
  const mapRef = useRef(null);
37
41
 
38
42
  const onDone = useCallback(() => {
@@ -83,7 +87,8 @@ const AddLocationMaps = memo(() => {
83
87
  {
84
88
  latitude: lat,
85
89
  longitude: lng,
86
- ...initialRegion,
90
+ latitudeDelta: MAP_INITIAL_REGION.LAT,
91
+ longitudeDelta: MAP_INITIAL_REGION.LNG,
87
92
  },
88
93
  600
89
94
  );
@@ -118,6 +123,7 @@ const AddLocationMaps = memo(() => {
118
123
  async (position) => {
119
124
  const currentLatitude = JSON.stringify(position.coords.latitude);
120
125
  const currentLongitude = JSON.stringify(position.coords.longitude);
126
+ setLoading(true);
121
127
  const { success, data } = await axiosGet(
122
128
  API.EXTERNAL.GOOGLE_MAP.GET_LOCATION_FROM_LAT_LNG,
123
129
  {
@@ -136,11 +142,26 @@ const AddLocationMaps = memo(() => {
136
142
  longitude: result.geometry.location.lng,
137
143
  });
138
144
  }
145
+ setLoading(false);
146
+ },
147
+ async (error) => {
148
+ if (error.code === GEOLOCATION_ERROR.PERMISSION_DENIED) {
149
+ const permissionResult = await check(keyPermission.LOCATION);
150
+ permissionResult === RESULTS.BLOCKED &&
151
+ OpenSetting(
152
+ t('location_rationale_title'),
153
+ t('location_require_message')
154
+ );
155
+ } else if (error.code === GEOLOCATION_ERROR.POSITION_UNAVAILABLE) {
156
+ const locationEnabaled = await openPromptEnableLocation();
157
+ process.env.NODE_ENV !== 'test' &&
158
+ locationEnabaled &&
159
+ getCurrentPosition();
160
+ }
139
161
  },
140
- (error) => {},
141
162
  {}
142
163
  );
143
- }, []);
164
+ }, [t]);
144
165
 
145
166
  const chooseOnMap = useCallback(() => {
146
167
  navigate(Routes.UnitStack, {
@@ -224,9 +245,10 @@ const AddLocationMaps = memo(() => {
224
245
  provider={PROVIDER_GOOGLE}
225
246
  style={styles.mapView}
226
247
  initialRegion={{
227
- ...initialRegion,
228
- latitude: DEFAULT_LATITUDE,
229
- longitude: DEFAULT_LONGITUDE,
248
+ latitude: EOH_LOCATION.LAT,
249
+ longitude: EOH_LOCATION.LNG,
250
+ latitudeDelta: MAP_INITIAL_REGION.LAT,
251
+ longitudeDelta: MAP_INITIAL_REGION.LNG,
230
252
  }}
231
253
  followUserLocation={true}
232
254
  >
@@ -266,6 +288,7 @@ const AddLocationMaps = memo(() => {
266
288
  onRightClick={onDone}
267
289
  rightDisabled={!input}
268
290
  />
291
+ {loading && <FullLoading />}
269
292
  </View>
270
293
  );
271
294
  });
@@ -12,7 +12,11 @@ import OptionsDropdownActionTemplate from '../../commons/OneTapTemplate/OptionsD
12
12
  import StatesGridActionTemplate from '../../commons/OneTapTemplate/StatesGridActionTemplate';
13
13
  import { axiosGet, axiosPost } from '../../utils/Apis/axios';
14
14
  import { API, Images } from '../../configs';
15
- import { CONDITION_TYPES, TESTID } from '../../configs/Constants';
15
+ import {
16
+ CONDITION_TYPES,
17
+ STATE_VALUE_SENSOR_TYPES,
18
+ TESTID,
19
+ } from '../../configs/Constants';
16
20
  import Routes from '../../utils/Route';
17
21
  import styles from './Styles/SelectActionStyles';
18
22
  import moment from 'moment';
@@ -286,6 +290,23 @@ const SelectAction = memo(({ route }) => {
286
290
  });
287
291
  };
288
292
 
293
+ const RenderCondition = useCallback(
294
+ (item) => {
295
+ const stateConditionData = STATE_VALUE_SENSOR_TYPES.find(
296
+ (i) => i.type === item?.sensor_type
297
+ );
298
+ const isNumberValue = !stateConditionData;
299
+
300
+ if (isNumberValue) {
301
+ return `${item?.name} ${
302
+ item?.title ? item.title : t('is_below') + ' (<)'
303
+ }{' '}${item?.value} ${item?.unit}`;
304
+ }
305
+ return t(stateConditionData?.stateValue[item?.value === 1 ? 1 : 0]);
306
+ },
307
+ [t]
308
+ );
309
+
289
310
  const rightComponent = useMemo(
290
311
  () => (
291
312
  <TouchableOpacity style={styles.closeButton} onPress={handleClose}>
@@ -326,7 +347,7 @@ const SelectAction = memo(({ route }) => {
326
347
  <LoadingSelectAction style={styles.container} />
327
348
  ) : (
328
349
  sensorData.map((item) => {
329
- const isHasValue = !!item?.value;
350
+ const isHasValue = !!item?.value || item?.value === 0;
330
351
  return (
331
352
  <View style={styles.wrapItem} key={item?.id}>
332
353
  <TitleCheckBox
@@ -351,9 +372,7 @@ const SelectAction = memo(({ route }) => {
351
372
  style={styles.description}
352
373
  >
353
374
  {isHasValue
354
- ? `${item?.name} ${
355
- item?.title ? item.title : t('is_below') + ' (<)'
356
- } ${item.value} ${item?.unit}`
375
+ ? RenderCondition(item)
357
376
  : `${t('no')} ${t('condition')}`}
358
377
  </Text>
359
378
  {isHasValue && (
@@ -13,11 +13,15 @@ import BottomButtonView from '../../commons/BottomButtonView';
13
13
  import { HorizontalPicker } from '../../commons';
14
14
  import { popAction } from '../../navigations/utils';
15
15
  import { useStatusBarPreview } from '../../hooks/Common/useStatusBar';
16
- import { CONDITION_TYPES } from '../../configs/Constants';
16
+ import {
17
+ CONDITION_TYPES,
18
+ STATE_VALUE_SENSOR_TYPES,
19
+ } from '../../configs/Constants';
17
20
 
18
21
  const SetUpSensor = () => {
19
22
  const t = useTranslations();
20
- const modalData = useMemo(
23
+
24
+ const modalNumberConditionData = useMemo(
21
25
  () => [
22
26
  {
23
27
  title: `${t('is_below')} (<)`,
@@ -41,9 +45,19 @@ const SetUpSensor = () => {
41
45
  const { goBack, dispatch } = useNavigation();
42
46
  const { params = {} } = useRoute();
43
47
  const { item, sensorData, setSensorData, isAutomateTab } = params;
48
+
49
+ const modalStateConditionData = useMemo(() => {
50
+ return STATE_VALUE_SENSOR_TYPES.find((i) => i.type === item?.sensor_type);
51
+ }, [item?.sensor_type]);
52
+
44
53
  const isHasLimit = useMemo(() => !!item?.range_max, [item]);
45
54
  const [isShowModal, setIsShowModal] = useState(false);
46
- const [itemActiveModal, setItemActiveModal] = useState(modalData[0]);
55
+ const [itemActiveModal, setItemActiveModal] = useState(
56
+ modalNumberConditionData[0]
57
+ );
58
+ const [itemActiveStateModal, setItemActiveStateModal] = useState(
59
+ modalStateConditionData?.stateValue[0]
60
+ );
47
61
  const [value, setValue] = useState(parseFloat(item?.value || 0));
48
62
  const [minimum] = useState(isHasLimit ? parseInt(item?.range_min, 10) : 0);
49
63
  const [maximum, setMaximum] = useState(
@@ -54,6 +68,10 @@ const SetUpSensor = () => {
54
68
  : (parseInt(item?.value, 10) + 20 - minimum) * 10 + minimum
55
69
  );
56
70
 
71
+ const isNumberValue = useMemo(() => {
72
+ return !(modalStateConditionData?.type === item?.sensor_type);
73
+ }, [item?.sensor_type, modalStateConditionData?.type]);
74
+
57
75
  const onOpenModal = useCallback(() => {
58
76
  setIsShowModal(true);
59
77
  }, []);
@@ -63,7 +81,13 @@ const SetUpSensor = () => {
63
81
  }, []);
64
82
 
65
83
  const onChooseCondition = (selectedItem) => () => {
66
- setItemActiveModal(selectedItem);
84
+ if (isNumberValue) {
85
+ setItemActiveModal(selectedItem);
86
+ } else {
87
+ setItemActiveStateModal(selectedItem);
88
+ setItemActiveModal(modalNumberConditionData[1]);
89
+ setValue(modalStateConditionData?.stateValue[0] === selectedItem ? 0 : 1);
90
+ }
67
91
  onCloseModal();
68
92
  };
69
93
 
@@ -134,7 +158,9 @@ const SetUpSensor = () => {
134
158
 
135
159
  useEffect(() => {
136
160
  if (item?.title) {
137
- const itemTemp = modalData.find((i) => i.title === item.title);
161
+ const itemTemp = modalNumberConditionData.find(
162
+ (i) => i.title === item.title
163
+ );
138
164
  itemTemp && setItemActiveModal(itemTemp);
139
165
  }
140
166
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -161,26 +187,30 @@ const SetUpSensor = () => {
161
187
  <Text type="Body" color={Colors.Gray7}>
162
188
  {t('condition')}
163
189
  </Text>
164
- <Text
165
- type="H4"
166
- semibold
167
- style={styles.desciption}
168
- >{`${item?.name} ${itemActiveModal.title}`}</Text>
190
+ <Text type="H4" semibold style={styles.desciption}>
191
+ {isNumberValue
192
+ ? `${item?.name} ${itemActiveModal.title}`
193
+ : t(itemActiveStateModal)}
194
+ </Text>
169
195
  <Image source={Images.arrowBack} style={styles.arrowRight} />
170
196
  </TouchableOpacity>
171
- <Text type="Body" color={Colors.Gray7} style={styles.setANumber}>
172
- {t('set_a_number')}
173
- </Text>
174
- <View style={[styles.flexRow, styles.center]}>
175
- <View>
176
- <Text style={styles.value}>{value}</Text>
177
- <View style={styles.underline} />
178
- </View>
179
- <Text type="H2" style={styles.unit}>
180
- {item?.unit}
181
- </Text>
182
- </View>
183
- {renderScroll}
197
+ {isNumberValue && (
198
+ <>
199
+ <Text type="Body" color={Colors.Gray7} style={styles.setANumber}>
200
+ {t('set_a_number')}
201
+ </Text>
202
+ <View style={[styles.flexRow, styles.center]}>
203
+ <View>
204
+ <Text style={styles.value}>{value}</Text>
205
+ <View style={styles.underline} />
206
+ </View>
207
+ <Text type="H2" style={styles.unit}>
208
+ {item?.unit}
209
+ </Text>
210
+ </View>
211
+ {renderScroll}
212
+ </>
213
+ )}
184
214
  </WrapHeaderScrollable>
185
215
  <BottomButtonView
186
216
  style={styles.bottomButtonView}
@@ -194,16 +224,21 @@ const SetUpSensor = () => {
194
224
  style={styles.modal}
195
225
  >
196
226
  <View style={styles.modalContent}>
197
- {modalData.map((i, index) => (
227
+ {(isNumberValue
228
+ ? modalNumberConditionData
229
+ : modalStateConditionData?.stateValue
230
+ ).map((i, index) => (
198
231
  <>
199
232
  <TouchableOpacity
200
233
  onPress={onChooseCondition(i)}
201
234
  key={index}
202
235
  style={styles.itemModal}
203
236
  >
204
- <Text type="H4">{`${item?.name} ${i.title}`}</Text>
237
+ <Text type="H4">
238
+ {isNumberValue ? `${item?.name} ${i.title}` : t(i)}
239
+ </Text>
205
240
  </TouchableOpacity>
206
- {index !== modalData.length - 1 && (
241
+ {index !== modalNumberConditionData.length - 1 && (
207
242
  <View style={styles.separated} />
208
243
  )}
209
244
  </>
@@ -56,6 +56,7 @@ describe('Test SetUpSensor', () => {
56
56
  range_max: 100,
57
57
  decimal_behind: 2,
58
58
  title: 'is below (<)',
59
+ sensor_type: 'air_quality',
59
60
  },
60
61
  sensorData: [],
61
62
  setSensorData: () => mockSetSensorData,
@@ -104,6 +105,7 @@ describe('Test SetUpSensor', () => {
104
105
  range_min: 0,
105
106
  decimal_behind: 2,
106
107
  title: 'is below (<)',
108
+ sensor_type: 'air_quality',
107
109
  },
108
110
  sensorData: [],
109
111
  setSensorData: () => mockSetSensorData,
@@ -25,6 +25,23 @@ jest.mock('react', () => {
25
25
  };
26
26
  });
27
27
 
28
+ jest.mock('react-native-onesignal', () => {
29
+ return {
30
+ disablePush: jest.fn(),
31
+ init: jest.fn(),
32
+ inFocusDisplaying: jest.fn(),
33
+ addEventListener: jest.fn(),
34
+ setNotificationWillShowInForegroundHandler: jest.fn(),
35
+ };
36
+ });
37
+
38
+ jest.mock('../../../hooks', () => {
39
+ return {
40
+ ...jest.requireActual('../../../hooks'),
41
+ useReceiveNotifications: jest.fn(() => ({ dataNotification: null })),
42
+ };
43
+ });
44
+
28
45
  describe('test Notification', () => {
29
46
  let tree;
30
47
 
@@ -14,6 +14,7 @@ import {
14
14
  } from '../../utils/Monitor';
15
15
  import { useSCContextSelector } from '../../context';
16
16
  import { notImplemented } from '../../utils/Utils';
17
+ import { useReceiveNotifications } from '../../hooks';
17
18
 
18
19
  let page = 1;
19
20
 
@@ -22,6 +23,7 @@ const Notification = memo(() => {
22
23
  const user = useSCContextSelector((state) => state?.auth?.account?.user);
23
24
  const [notifications, setNotifications] = useState([]);
24
25
  const [maxPageNotification, setMaxPageNotification] = useState(1);
26
+
25
27
  const rightComponent = useMemo(
26
28
  () => (
27
29
  <View style={styles.rightComponent}>
@@ -79,6 +81,8 @@ const Notification = memo(() => {
79
81
  }
80
82
  }, []);
81
83
 
84
+ useReceiveNotifications(onRefresh);
85
+
82
86
  useEffect(() => {
83
87
  watchNotificationData(user, onRefresh);
84
88
  return () => unwatchNotificationData(user);
@@ -66,7 +66,9 @@ const SelectPermission = ({ route }) => {
66
66
  setDataStations(data);
67
67
  } else {
68
68
  const data = [...dataStationTemp];
69
+
69
70
  const index = data.findIndex((item) => item.id === idGroup);
71
+ // Remove sensors when end_devices deloy production
70
72
  data[index] = {
71
73
  ...data[index],
72
74
  isChecked,
@@ -76,12 +78,29 @@ const SelectPermission = ({ route }) => {
76
78
  actions: i.actions.map((j) => ({ ...j, isChecked })),
77
79
  read_configs: i.read_configs.map((j) => ({ ...j, isChecked })),
78
80
  })),
81
+ sensors: data[index]?.sensors.map((i) => ({
82
+ ...i,
83
+ isChecked,
84
+ actions: i.actions.map((j) => ({ ...j, isChecked })),
85
+ read_configs: i.read_configs.map((j) => ({ ...j, isChecked })),
86
+ })),
79
87
  };
80
88
  setDataStations(data);
81
89
  setIsTickAllDevices(!data.some((item) => !item.isChecked));
82
90
  }
83
91
  };
84
92
 
93
+ const stationCheck = (data) => {
94
+ for (let station of data) {
95
+ if (station.sensors.length) {
96
+ station.isChecked = !station.sensors.some((i) => !i.isChecked);
97
+ }
98
+ }
99
+ setIsTickAllDevices(!dataStationTemp.some((i) => !i.isChecked));
100
+ dataStationTemp = data;
101
+ setDataStations(data);
102
+ };
103
+
85
104
  const onTickedChild = (
86
105
  idGroup,
87
106
  sensorId,
@@ -105,17 +124,6 @@ const SelectPermission = ({ route }) => {
105
124
  stationCheck(data);
106
125
  };
107
126
 
108
- const stationCheck = (data) => {
109
- for (let station of data) {
110
- if (station.sensors.length) {
111
- station.isChecked = !station.sensors.some((i) => !i.isChecked);
112
- }
113
- }
114
- setIsTickAllDevices(!dataStationTemp.some((i) => !i.isChecked));
115
- dataStationTemp = data;
116
- setDataStations(data);
117
- };
118
-
119
127
  const onTickedSensor = (idGroup, sensorId, isChecked) => {
120
128
  let data = [...dataStationTemp];
121
129
  const group = data.find((i) => i.id === idGroup);
@@ -296,7 +296,7 @@ const UnitDetail = ({ route }) => {
296
296
  const to = setTimeout(() => {
297
297
  setAction(Action.IS_FIRST_OPEN_CAMERA, false);
298
298
  clearTimeout(to);
299
- }, 5000);
299
+ }, 3000);
300
300
  }
301
301
  }, [isFirstOpenCamera, setAction]);
302
302
 
@@ -317,10 +317,11 @@ const UnitDetail = ({ route }) => {
317
317
  {/* NOTE: This is a trick to fix camera not full screen on first open app */}
318
318
  {isFirstOpenCamera && (
319
319
  <MediaPlayerDetail
320
- uri={Constants.URL_STREAM_BALCONY_CAMERA}
320
+ uri={Constants.URL_STREAM_CAMERA_DEMO}
321
321
  isPaused={false}
322
322
  width={1}
323
323
  height={1}
324
+ style={styles.fakeCamera}
324
325
  />
325
326
  )}
326
327
 
@@ -84,4 +84,8 @@ export default StyleSheet.create({
84
84
  width: 1,
85
85
  height: 1,
86
86
  },
87
+ fakeCamera: {
88
+ position: 'absolute',
89
+ zIndex: -99999,
90
+ },
87
91
  });
@@ -991,5 +991,9 @@
991
991
  "camera_request_permission_des": "To use camera, please unlock camera permission",
992
992
  "location_require_message": "EoH needs your location permission to get current location.",
993
993
  "turn_on_location_service": "Turn on location service for your phone",
994
- "turn_on_location_service_des": "To continue, let your device turn on location by turning on location service in Settings"
994
+ "turn_on_location_service_des": "To continue, let your device turn on location by turning on location service in Settings",
995
+ "detected": "Detected",
996
+ "not_detected": "Not detected",
997
+ "activated": "Activated",
998
+ "not_activated": "Not activated"
995
999
  }
@@ -992,5 +992,9 @@
992
992
  "camera_request_permission_des": "Để sử dụng máy ảnh, vui lòng mở khóa quyền đối với máy ảnh",
993
993
  "location_require_message": "Eoh cần quyền truy cập vị trí của bạn để lấy vị trí hiện tại.",
994
994
  "turn_on_location_service": "Bật dịch vụ vị trí cho điện thoại của bạn",
995
- "turn_on_location_service_des": "Để tiếp tục, bật dịch vụ vị trí cho điện thoại của bạn trong Cài Đặt"
995
+ "turn_on_location_service_des": "Để tiếp tục, bật dịch vụ vị trí cho điện thoại của bạn trong Cài Đặt",
996
+ "detected": "Phát hiện",
997
+ "not_detected": "Không phát hiện",
998
+ "activated": "Được kích hoạt",
999
+ "not_activated": "Không kích hoạt"
996
1000
  }
@@ -115,6 +115,7 @@ export const object_Ids = (data) => {
115
115
  let configIds = [];
116
116
  data?.forEach((station) => {
117
117
  stationIds.push(station?.id);
118
+ //Todo change devices When production release end_devices
118
119
  station?.sensors?.forEach((sensor) => {
119
120
  sensorIds.push(sensor?.id);
120
121
  sensor?.actions?.forEach((action) => {