@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 +1 -1
- package/src/commons/MediaPlayerDetail/index.js +3 -0
- package/src/commons/UnitSummary/ConfigHistoryChart/__test__/ConfigHistoryChart.test.js +28 -1
- package/src/commons/UnitSummary/ConfigHistoryChart.js +57 -24
- package/src/configs/API.js +1 -1
- package/src/configs/Constants.js +23 -4
- package/src/context/actionType.ts +1 -0
- package/src/context/mockStore.ts +1 -0
- package/src/context/reducer.ts +7 -1
- package/src/hooks/useReceiveNotifications.js +3 -2
- package/src/screens/AddLocationMaps/__test__/index.test.js +265 -0
- package/src/screens/AddLocationMaps/index.js +39 -16
- package/src/screens/AddNewAction/SelectAction.js +24 -5
- package/src/screens/AddNewAction/SetupSensor.js +61 -26
- package/src/screens/AddNewAction/__test__/SetupSensor.test.js +2 -0
- package/src/screens/Notification/__test__/Notification.test.js +17 -0
- package/src/screens/Notification/index.js +4 -0
- package/src/screens/Sharing/SelectPermission.js +19 -11
- package/src/screens/Unit/Detail.js +3 -2
- package/src/screens/Unit/styles.js +4 -0
- package/src/utils/I18n/translations/en.json +5 -1
- package/src/utils/I18n/translations/vi.json +5 -1
- package/src/utils/Utils.js +1 -0
package/package.json
CHANGED
|
@@ -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
|
|
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.
|
|
48
|
-
params.append('date_to', endDate.
|
|
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
|
-
|
|
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
|
package/src/configs/API.js
CHANGED
|
@@ -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/
|
|
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/`,
|
package/src/configs/Constants.js
CHANGED
|
@@ -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
|
-
|
|
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';
|
package/src/context/mockStore.ts
CHANGED
package/src/context/reducer.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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 {
|
|
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
|
-
?
|
|
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 {
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
{
|
|
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">
|
|
237
|
+
<Text type="H4">
|
|
238
|
+
{isNumberValue ? `${item?.name} ${i.title}` : t(i)}
|
|
239
|
+
</Text>
|
|
205
240
|
</TouchableOpacity>
|
|
206
|
-
{index !==
|
|
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
|
-
},
|
|
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.
|
|
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
|
|
|
@@ -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
|
}
|
package/src/utils/Utils.js
CHANGED
|
@@ -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) => {
|