@eohjsc/react-native-smart-city 0.2.57 → 0.2.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -22
- package/src/commons/Auth/__test__/AccountList.test.js +33 -0
- package/src/commons/CameraDevice/index.js +2 -0
- package/src/commons/CardShadow/index.js +1 -1
- package/src/commons/CardShadow/styles.js +1 -3
- package/src/commons/Device/LinearChart.js +0 -34
- package/src/commons/MediaPlayerDetail/index.js +160 -160
- package/src/commons/Sharing/RowMember.js +4 -2
- package/src/commons/Sharing/__test__/RowMember.test.js +42 -0
- package/src/commons/SubUnit/ShortDetail.js +11 -5
- package/src/commons/UnitSummary/AirQuality/SegmentedRoundDisplay/index.js +1 -1
- package/src/configs/API.js +5 -0
- package/src/configs/Constants.js +1 -1
- package/src/configs/SCConfig.js +8 -0
- package/src/iot/RemoteControl/Bluetooth.js +14 -0
- package/src/iot/RemoteControl/index.js +0 -1
- package/src/screens/AddCommon/SelectSubUnit.js +23 -1
- package/src/screens/AddCommon/SelectUnit.js +12 -0
- package/src/screens/Device/__test__/detail.test.js +0 -5
- package/src/screens/Device/components/SensorDisplayItem.js +10 -10
- package/src/screens/Device/detail.js +15 -1
- package/src/screens/Device/hooks/useDisconnectedDevice.js +31 -26
- package/src/screens/Sharing/MemberList.js +2 -9
- package/src/screens/TDSGuide/index.js +6 -4
- package/src/screens/Unit/ManageUnit/index.test.js +34 -0
- package/src/utils/Apis/axios.js +17 -5
- package/src/utils/I18n/translations/en.json +2 -1
- package/src/utils/I18n/translations/vi.json +2 -1
- package/src/utils/Utils.js +22 -2
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.
|
|
4
|
+
"version": "0.2.58",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -43,11 +43,7 @@
|
|
|
43
43
|
"pods": "cd example && pod-install --quiet",
|
|
44
44
|
"bootstrap": "yarn example && yarn && yarn pods",
|
|
45
45
|
"build": "sync-files ./src ../EohMobile/node_modules/@eohjsc/react-native-smart-city/src",
|
|
46
|
-
"watch": "sync-files --watch ./src ../EohMobile/node_modules/@eohjsc/react-native-smart-city/src"
|
|
47
|
-
"test:code-formatter": "prettier src/**/**/*.js --write",
|
|
48
|
-
"test:code-formatter-step-1": "prettier --check src/**/*.js",
|
|
49
|
-
"test:code-formatter-step-2": "prettier --check src/**/**/*.js",
|
|
50
|
-
"test:code-formatter-step-3": "prettier --check src/**/**/**/*.js"
|
|
46
|
+
"watch": "sync-files --watch ./src ../EohMobile/node_modules/@eohjsc/react-native-smart-city/src"
|
|
51
47
|
},
|
|
52
48
|
"repository": {
|
|
53
49
|
"type": "git",
|
|
@@ -85,7 +81,6 @@
|
|
|
85
81
|
"eslint-plugin-react": "^7.21.5",
|
|
86
82
|
"eslint-plugin-react-native": "^3.10.0",
|
|
87
83
|
"factory-girl": "^5.0.4",
|
|
88
|
-
"husky": "^2.7.0",
|
|
89
84
|
"jest": "^26.6.3",
|
|
90
85
|
"jest-circus": "^26.6.3",
|
|
91
86
|
"jetifier": "^1.6.6",
|
|
@@ -146,6 +141,7 @@
|
|
|
146
141
|
"react-hooks-global-state": "^1.0.1",
|
|
147
142
|
"react-i18next": "^11.8.12",
|
|
148
143
|
"react-native-alert-async": "^1.0.5",
|
|
144
|
+
"react-native-android-wifi": "^0.0.41",
|
|
149
145
|
"react-native-appearance": "^0.3.4",
|
|
150
146
|
"react-native-base64": "^0.1.0",
|
|
151
147
|
"react-native-ble-plx": "^2.0.1",
|
|
@@ -206,21 +202,6 @@
|
|
|
206
202
|
"validator": "^13.1.1",
|
|
207
203
|
"victory-native": "^35.0.1"
|
|
208
204
|
},
|
|
209
|
-
"husky": {
|
|
210
|
-
"hooks": {
|
|
211
|
-
"pre-commit": "lint-staged",
|
|
212
|
-
"pre-push": "yarn jest"
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
"lint-staged": {
|
|
216
|
-
"*.{js, ts}": [
|
|
217
|
-
"yarn lint",
|
|
218
|
-
"yarn test:code-formatter",
|
|
219
|
-
"yarn test:code-formatter-step-1",
|
|
220
|
-
"yarn test:code-formatter-step-2",
|
|
221
|
-
"yarn test:code-formatter-step-3"
|
|
222
|
-
]
|
|
223
|
-
},
|
|
224
205
|
"bugs": {
|
|
225
206
|
"url": "https://github.com/github_account/react-native-smart-city/issues"
|
|
226
207
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView } from 'react-native';
|
|
3
|
+
import { act, create } from 'react-test-renderer';
|
|
4
|
+
import AccountList from '../AccountList';
|
|
5
|
+
|
|
6
|
+
describe('Test AccountList', () => {
|
|
7
|
+
let tree;
|
|
8
|
+
const accounts = [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
name: 'test',
|
|
12
|
+
phone_number: 123456677,
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
it('test render has data', async () => {
|
|
17
|
+
await act(() => {
|
|
18
|
+
tree = create(<AccountList accounts={accounts} />);
|
|
19
|
+
});
|
|
20
|
+
const instance = tree.root;
|
|
21
|
+
const scrollViews = instance.findAllByType(ScrollView);
|
|
22
|
+
expect(scrollViews).toHaveLength(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('test render has no data', async () => {
|
|
26
|
+
await act(() => {
|
|
27
|
+
tree = create(<AccountList accounts={[]} />);
|
|
28
|
+
});
|
|
29
|
+
const instance = tree.root;
|
|
30
|
+
const scrollViews = instance.findAllByType(ScrollView);
|
|
31
|
+
expect(scrollViews).toHaveLength(1);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -59,6 +59,8 @@ const CameraDevice = ({ station, handleFullScreen, goToPlayBack }) => {
|
|
|
59
59
|
cameraName={device.configuration.name}
|
|
60
60
|
handleFullScreen={handleFullScreen}
|
|
61
61
|
goToPlayBack={goToPlayBack(device, { uri: station?.background })}
|
|
62
|
+
width={standardizeWidth}
|
|
63
|
+
height={standardizeHeight}
|
|
62
64
|
/>
|
|
63
65
|
</View>
|
|
64
66
|
))}
|
|
@@ -3,11 +3,9 @@ import { StyleSheet } from 'react-native';
|
|
|
3
3
|
|
|
4
4
|
export default StyleSheet.create({
|
|
5
5
|
card: {
|
|
6
|
-
|
|
7
|
-
marginBottom: 14,
|
|
6
|
+
marginVertical: 14,
|
|
8
7
|
marginHorizontal: 16,
|
|
9
8
|
padding: 16,
|
|
10
|
-
flexDirection: 'column',
|
|
11
9
|
backgroundColor: Colors.White,
|
|
12
10
|
borderRadius: 10,
|
|
13
11
|
shadowColor: Colors.Shadow,
|
|
@@ -56,40 +56,6 @@ const chartOptions = {
|
|
|
56
56
|
},
|
|
57
57
|
minRange: 3600 * 24 * 1000,
|
|
58
58
|
},
|
|
59
|
-
plotOptions: {
|
|
60
|
-
series: {
|
|
61
|
-
point: {
|
|
62
|
-
events: {
|
|
63
|
-
click: () => {
|
|
64
|
-
let series = this.series.chart.series;
|
|
65
|
-
const seriesIndex = this.series.index;
|
|
66
|
-
for (let i = 0; i < series.length; i++) {
|
|
67
|
-
if (series[i].index !== seriesIndex) {
|
|
68
|
-
series[i].visible ? series[i].hide() : series[i].show();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
events: {
|
|
76
|
-
legendItemClick: (event) => {
|
|
77
|
-
if (!this.visible) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
let seriesIndex = this.index;
|
|
81
|
-
let series = this.chart.series;
|
|
82
|
-
for (let i = 0; i < series.length; i++) {
|
|
83
|
-
if (series[i].index !== seriesIndex) {
|
|
84
|
-
series[i].visible ? series[i].hide() : series[i].show();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return false;
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
showInNavigator: true,
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
59
|
};
|
|
94
60
|
|
|
95
61
|
function LinearChart({ datas }) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { memo, useCallback, useState, useEffect } from 'react';
|
|
2
2
|
import { Image, View, StyleSheet, Text, TouchableOpacity } from 'react-native';
|
|
3
3
|
import { VLCPlayer } from 'react-native-vlc-media-player';
|
|
4
|
-
import { getStatusBarHeight } from 'react-native-iphone-x-helper';
|
|
5
4
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
6
5
|
|
|
7
6
|
import PauseIcon from '../../../assets/images/Common/Pause.svg';
|
|
@@ -11,173 +10,174 @@ import styles from './Styles/MediaPlayerDetailStyles';
|
|
|
11
10
|
import FImage from '../../commons/FImage';
|
|
12
11
|
import { TESTID } from '../../configs/Constants';
|
|
13
12
|
|
|
14
|
-
const MediaPlayerDetail =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
const MediaPlayerDetail = ({
|
|
14
|
+
uri,
|
|
15
|
+
cameraName,
|
|
16
|
+
thumbnail,
|
|
17
|
+
style,
|
|
18
|
+
wrapStyles,
|
|
19
|
+
resizeMode = 'none',
|
|
20
|
+
amount,
|
|
21
|
+
handleFullScreen,
|
|
22
|
+
isPaused = true,
|
|
23
|
+
goToPlayBack,
|
|
24
|
+
isShowFullScreenIcon = false,
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
}) => {
|
|
28
|
+
const t = useTranslations();
|
|
29
|
+
const [paused, setPaused] = useState(isPaused);
|
|
30
|
+
const onTapPause = useCallback(() => {
|
|
31
|
+
setPaused(false);
|
|
32
|
+
}, []);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
const onTapGoDetail = useCallback(() => {
|
|
35
|
+
if (!paused) {
|
|
36
|
+
setPaused(true);
|
|
37
|
+
} else {
|
|
38
|
+
goToPlayBack && goToPlayBack();
|
|
39
|
+
}
|
|
40
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
+
}, [paused]);
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const onFullScreen = useCallback(() => {
|
|
44
|
+
handleFullScreen && handleFullScreen({ uri, cameraName, thumbnail });
|
|
45
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
+
}, []);
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const getWidthHeight = useCallback(() => {
|
|
49
|
+
let width = 0,
|
|
50
|
+
height = 0;
|
|
51
|
+
switch (amount) {
|
|
52
|
+
case 1:
|
|
53
|
+
width = Constants.width;
|
|
54
|
+
height = 224;
|
|
55
|
+
break;
|
|
56
|
+
case 4:
|
|
57
|
+
width = Constants.width / 2;
|
|
58
|
+
height = 112;
|
|
59
|
+
break;
|
|
60
|
+
case 6:
|
|
61
|
+
width = Constants.width / 3;
|
|
62
|
+
height = 112;
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
width = Constants.width - 28;
|
|
66
|
+
height = 162;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
width,
|
|
71
|
+
height,
|
|
72
|
+
};
|
|
73
|
+
}, [amount]);
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
source={source}
|
|
73
|
-
style={[
|
|
74
|
-
styles.player,
|
|
75
|
-
style,
|
|
76
|
-
isFullScreen && {
|
|
77
|
-
marginLeft: -Constants.width / 2 - getStatusBarHeight(),
|
|
78
|
-
width: Constants.height,
|
|
79
|
-
height: Constants.width,
|
|
80
|
-
transform: [{ rotate: '90deg' }],
|
|
81
|
-
},
|
|
82
|
-
]}
|
|
83
|
-
defaultSource={Images.BgDevice}
|
|
84
|
-
resizeMode="cover"
|
|
85
|
-
/>
|
|
86
|
-
<View
|
|
87
|
-
style={[
|
|
88
|
-
StyleSheet.absoluteFillObject,
|
|
89
|
-
{ backgroundColor: colorOpacity(Colors.Black, 0.3) },
|
|
90
|
-
]}
|
|
91
|
-
/>
|
|
92
|
-
</View>
|
|
93
|
-
) : isFullScreen ? (
|
|
94
|
-
<View style={{ transform: [{ rotate: '90deg' }] }}>
|
|
95
|
-
<VLCPlayer
|
|
96
|
-
autoAspectRatio={true}
|
|
97
|
-
videoAspectRatio={`${Constants.height}:${Constants.width}`}
|
|
98
|
-
source={{ uri }}
|
|
99
|
-
style={[
|
|
100
|
-
{
|
|
101
|
-
width: Constants.height,
|
|
102
|
-
height: Constants.width,
|
|
103
|
-
},
|
|
104
|
-
]}
|
|
105
|
-
/>
|
|
106
|
-
</View>
|
|
107
|
-
) : (
|
|
108
|
-
<VLCPlayer
|
|
109
|
-
autoAspectRatio={true}
|
|
110
|
-
videoAspectRatio={
|
|
111
|
-
!amount
|
|
112
|
-
? '21:9'
|
|
113
|
-
: amount === 1
|
|
114
|
-
? '15:9'
|
|
115
|
-
: amount === 4
|
|
116
|
-
? '5:3'
|
|
117
|
-
: '10:9'
|
|
118
|
-
}
|
|
119
|
-
source={{
|
|
120
|
-
initType: 2,
|
|
121
|
-
hwDecoderEnabled: 1,
|
|
122
|
-
hwDecoderForced: 1,
|
|
123
|
-
uri,
|
|
124
|
-
initOptions: [
|
|
125
|
-
'--no-audio',
|
|
126
|
-
'--rtsp-tcp',
|
|
127
|
-
'--network-caching=150',
|
|
128
|
-
'--rtsp-caching=150',
|
|
129
|
-
'--no-stats',
|
|
130
|
-
'--tcp-caching=150',
|
|
131
|
-
'--realrtsp-caching=150',
|
|
132
|
-
],
|
|
133
|
-
}}
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
setPaused(isPaused);
|
|
77
|
+
}, [isPaused]);
|
|
78
|
+
|
|
79
|
+
const source = !thumbnail || !thumbnail.uri ? Images.BgDevice : thumbnail;
|
|
80
|
+
return (
|
|
81
|
+
<View style={[styles.wrap, wrapStyles]}>
|
|
82
|
+
<View style={[styles.loadingWrap]}>
|
|
83
|
+
<Text style={styles.loadingText}>{t('loading')}</Text>
|
|
84
|
+
</View>
|
|
85
|
+
<TouchableOpacity
|
|
86
|
+
activeOpacity={1}
|
|
87
|
+
style={styles.videoBtn}
|
|
88
|
+
onPress={onTapGoDetail}
|
|
89
|
+
>
|
|
90
|
+
{paused ? (
|
|
91
|
+
<View style={[styles.player, style]}>
|
|
92
|
+
<FImage
|
|
93
|
+
source={source}
|
|
134
94
|
style={[styles.player, style]}
|
|
135
|
-
|
|
136
|
-
|
|
95
|
+
defaultSource={Images.BgDevice}
|
|
96
|
+
resizeMode="cover"
|
|
97
|
+
/>
|
|
98
|
+
<View
|
|
99
|
+
style={[
|
|
100
|
+
StyleSheet.absoluteFillObject,
|
|
101
|
+
{ backgroundColor: colorOpacity(Colors.Black, 0.3) },
|
|
102
|
+
]}
|
|
137
103
|
/>
|
|
138
|
-
)}
|
|
139
|
-
|
|
140
|
-
<View style={styles.buttonView}>
|
|
141
|
-
<View style={styles.buttonPause}>
|
|
142
|
-
<TouchableOpacity
|
|
143
|
-
onPress={onTapPause}
|
|
144
|
-
style={[
|
|
145
|
-
styles.btn,
|
|
146
|
-
isFullScreen && { transform: [{ rotate: '90deg' }] },
|
|
147
|
-
]}
|
|
148
|
-
activeOpacity={0.8}
|
|
149
|
-
>
|
|
150
|
-
{paused && <PauseIcon />}
|
|
151
|
-
</TouchableOpacity>
|
|
152
|
-
</View>
|
|
153
104
|
</View>
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
105
|
+
) : (
|
|
106
|
+
<VLCPlayer
|
|
107
|
+
autoAspectRatio={true}
|
|
108
|
+
videoAspectRatio={
|
|
109
|
+
width && height
|
|
110
|
+
? `${width}:${height}`
|
|
111
|
+
: `${getWidthHeight().width}:${getWidthHeight().height}`
|
|
112
|
+
}
|
|
113
|
+
source={{
|
|
114
|
+
initType: 2,
|
|
115
|
+
hwDecoderEnabled: 1,
|
|
116
|
+
hwDecoderForced: 1,
|
|
117
|
+
uri,
|
|
118
|
+
initOptions: [
|
|
119
|
+
'--no-audio',
|
|
120
|
+
'--rtsp-tcp',
|
|
121
|
+
'--network-caching=150',
|
|
122
|
+
'--rtsp-caching=150',
|
|
123
|
+
'--no-stats',
|
|
124
|
+
'--tcp-caching=150',
|
|
125
|
+
'--realrtsp-caching=150',
|
|
126
|
+
// '--avcodec-threads=20',
|
|
127
|
+
// '--live-cacheing=150',
|
|
128
|
+
],
|
|
129
|
+
}}
|
|
169
130
|
style={[
|
|
170
|
-
styles.
|
|
171
|
-
|
|
131
|
+
styles.player,
|
|
132
|
+
style,
|
|
133
|
+
{
|
|
134
|
+
width: width || getWidthHeight().width,
|
|
135
|
+
height: height || getWidthHeight().height,
|
|
136
|
+
},
|
|
172
137
|
]}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
138
|
+
// resizeMode={resizeMode}
|
|
139
|
+
isLive={true}
|
|
140
|
+
// playInBackground={true}
|
|
141
|
+
/>
|
|
177
142
|
)}
|
|
178
|
-
</View>
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
143
|
|
|
183
|
-
|
|
144
|
+
<View style={styles.buttonView}>
|
|
145
|
+
<View style={styles.buttonPause}>
|
|
146
|
+
<TouchableOpacity
|
|
147
|
+
onPress={onTapPause}
|
|
148
|
+
style={[styles.btn]}
|
|
149
|
+
activeOpacity={0.8}
|
|
150
|
+
>
|
|
151
|
+
{paused && <PauseIcon />}
|
|
152
|
+
</TouchableOpacity>
|
|
153
|
+
</View>
|
|
154
|
+
</View>
|
|
155
|
+
</TouchableOpacity>
|
|
156
|
+
|
|
157
|
+
{cameraName && paused && (
|
|
158
|
+
<Text
|
|
159
|
+
style={[
|
|
160
|
+
styles.cameraName,
|
|
161
|
+
amount && amount !== 1 && styles.cameraName2,
|
|
162
|
+
]}
|
|
163
|
+
>
|
|
164
|
+
{cameraName}
|
|
165
|
+
</Text>
|
|
166
|
+
)}
|
|
167
|
+
{isShowFullScreenIcon && (
|
|
168
|
+
<TouchableOpacity
|
|
169
|
+
onPress={onFullScreen}
|
|
170
|
+
style={[
|
|
171
|
+
styles.iconFullScreen,
|
|
172
|
+
(amount === 4 || amount === 6) && styles.iconFullScreen2,
|
|
173
|
+
]}
|
|
174
|
+
testID={TESTID.SUB_UNIT_FULL_CAMERA}
|
|
175
|
+
>
|
|
176
|
+
<Image source={Images.fullscreen} />
|
|
177
|
+
</TouchableOpacity>
|
|
178
|
+
)}
|
|
179
|
+
</View>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default memo(MediaPlayerDetail);
|
|
@@ -5,6 +5,7 @@ import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
|
5
5
|
|
|
6
6
|
import { Colors } from '../../configs';
|
|
7
7
|
import Text from '../../commons/Text';
|
|
8
|
+
import { shortEmailName } from '../../utils/Utils';
|
|
8
9
|
|
|
9
10
|
import BtnRemoveMember from './BtnRemoveMember';
|
|
10
11
|
|
|
@@ -37,7 +38,6 @@ const RowMember = memo(
|
|
|
37
38
|
if (member?.id === ownerId && member?.share_id) {
|
|
38
39
|
return null;
|
|
39
40
|
}
|
|
40
|
-
|
|
41
41
|
return (
|
|
42
42
|
<View style={styles.rowContainer}>
|
|
43
43
|
<View
|
|
@@ -49,7 +49,9 @@ const RowMember = memo(
|
|
|
49
49
|
<IconOutline name={'user'} size={20} color={Colors.White} />
|
|
50
50
|
</View>
|
|
51
51
|
<View style={[styles.infoContainer, { paddingBottom: paddingBottom }]}>
|
|
52
|
-
<Text style={styles.textName}>
|
|
52
|
+
<Text style={styles.textName}>
|
|
53
|
+
{member?.name || shortEmailName(member.email) || ''}
|
|
54
|
+
</Text>
|
|
53
55
|
{!!role && (
|
|
54
56
|
<Text style={[styles.textRole, { color: roleColor }]}>{role}</Text>
|
|
55
57
|
)}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderer, { act } from 'react-test-renderer';
|
|
3
|
+
import RowMember from '../RowMember';
|
|
4
|
+
import Text from '../../Text';
|
|
5
|
+
import { SCProvider } from '../../../context';
|
|
6
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
7
|
+
|
|
8
|
+
const wrapComponent = (member, ownerId, currentUserId, mockFunc) => (
|
|
9
|
+
<SCProvider initState={mockSCStore({})}>
|
|
10
|
+
<RowMember
|
|
11
|
+
member={member}
|
|
12
|
+
ownerId={ownerId}
|
|
13
|
+
currentUserId={currentUserId}
|
|
14
|
+
onPressRemove={mockFunc}
|
|
15
|
+
/>
|
|
16
|
+
</SCProvider>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe('RowMember', () => {
|
|
20
|
+
let tree;
|
|
21
|
+
const mockFunc = jest.fn();
|
|
22
|
+
test('RowMember owner have name', () => {
|
|
23
|
+
const dataMember = { id: 1, name: 'CEO' };
|
|
24
|
+
act(() => {
|
|
25
|
+
tree = renderer.create(wrapComponent(dataMember, 1, 1, mockFunc));
|
|
26
|
+
});
|
|
27
|
+
const instance = tree.root;
|
|
28
|
+
const textInputs = instance.findAllByType(Text);
|
|
29
|
+
expect(textInputs.length).toBe(2);
|
|
30
|
+
expect(textInputs[0].props.children).toEqual('CEO');
|
|
31
|
+
});
|
|
32
|
+
test('RowMember owner dont have name show start of email ', () => {
|
|
33
|
+
const dataMember = { id: 1, name: '', email: 'abc@gmail.com' };
|
|
34
|
+
act(() => {
|
|
35
|
+
tree = renderer.create(wrapComponent(dataMember, 1, 1, mockFunc));
|
|
36
|
+
});
|
|
37
|
+
const instance = tree.root;
|
|
38
|
+
const textInputs = instance.findAllByType(Text);
|
|
39
|
+
expect(textInputs.length).toBe(2);
|
|
40
|
+
expect(textInputs[0].props.children).toEqual('abc');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -9,10 +9,10 @@ import { Section } from '../Section';
|
|
|
9
9
|
import Text from '../Text';
|
|
10
10
|
import ItemDevice from '../Device/ItemDevice';
|
|
11
11
|
import ItemAddNew from '../Device/ItemAddNew';
|
|
12
|
-
import MediaPlayer from '../MediaPlayer';
|
|
13
12
|
import { standardizeCameraScreenSize } from '../../utils/Utils';
|
|
14
13
|
import Routes from '../../utils/Route';
|
|
15
14
|
import FastImage from 'react-native-fast-image';
|
|
15
|
+
import MediaPlayerDetail from '../MediaPlayerDetail';
|
|
16
16
|
|
|
17
17
|
const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
|
|
18
18
|
Device.screenWidth - 32
|
|
@@ -21,23 +21,29 @@ const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
|
|
|
21
21
|
const ShortDetailSubUnit = ({ unit, station, isGGHomeConnected }) => {
|
|
22
22
|
const t = useTranslations();
|
|
23
23
|
const { navigate } = useNavigation();
|
|
24
|
+
|
|
24
25
|
const renderCamera = () => {
|
|
25
26
|
if (station?.camera) {
|
|
26
27
|
return (
|
|
27
28
|
<View
|
|
28
29
|
style={[
|
|
29
30
|
styles.boxImage,
|
|
30
|
-
{
|
|
31
|
+
{
|
|
32
|
+
width: standardizeWidth,
|
|
33
|
+
height: standardizeHeight,
|
|
34
|
+
},
|
|
31
35
|
]}
|
|
32
36
|
testID={TESTID.SUB_UNIT_CAMERA_VIEW}
|
|
33
37
|
>
|
|
34
|
-
<
|
|
38
|
+
<MediaPlayerDetail
|
|
35
39
|
uri={station.camera.uri}
|
|
36
|
-
previewUri={station.camera.preview_uri}
|
|
37
40
|
thumbnail={{
|
|
38
41
|
uri: station.background,
|
|
39
42
|
}}
|
|
40
|
-
key={`camera-${station
|
|
43
|
+
key={`camera-device-${station?.camera?.id}`}
|
|
44
|
+
cameraName={station?.camera?.name}
|
|
45
|
+
width={standardizeWidth}
|
|
46
|
+
height={standardizeHeight}
|
|
41
47
|
/>
|
|
42
48
|
</View>
|
|
43
49
|
);
|
|
@@ -163,7 +163,7 @@ const SegmentedRoundDisplay = ({
|
|
|
163
163
|
<Text
|
|
164
164
|
x={svgWidth / 2 + 50}
|
|
165
165
|
fontWeight="normal"
|
|
166
|
-
fontSize={valueText.toString().length >= 6 ?
|
|
166
|
+
fontSize={valueText.toString().length >= 6 ? 35 : 56}
|
|
167
167
|
y={svgHeight / 2 + 30}
|
|
168
168
|
fill={filledArcColor}
|
|
169
169
|
textAnchor="middle"
|
package/src/configs/API.js
CHANGED
|
@@ -172,6 +172,11 @@ const API = {
|
|
|
172
172
|
SCConfig.apiRoot +
|
|
173
173
|
`/connection_manager/lg_thinq/device_status/${sensorId}/`,
|
|
174
174
|
},
|
|
175
|
+
VCONNEX: {
|
|
176
|
+
AUTHORIZE: (client_id, redirect_uri, user_id, station_id) =>
|
|
177
|
+
// eslint-disable-next-line max-len
|
|
178
|
+
`https://partner-api-stg.vconnex.vn/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=code&scope=SYNCH&scope=CONTROL&scope=QUERY&state=${user_id}@${station_id}`,
|
|
179
|
+
},
|
|
175
180
|
},
|
|
176
181
|
NOTIFICATION: {
|
|
177
182
|
LIST_ALL_NOTIFICATIONS: (page, type) =>
|
package/src/configs/Constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Platform, Dimensions, StatusBar } from 'react-native';
|
|
2
2
|
import { RFValue } from 'react-native-responsive-fontsize';
|
|
3
|
-
import OneTap from '../../assets/images/OneTap
|
|
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
6
|
|
package/src/configs/SCConfig.js
CHANGED
|
@@ -90,6 +90,8 @@ const SCDefaultConfig = {
|
|
|
90
90
|
LG_CLIENT_ID: '2b85aee334f046848341547894bb7c4e',
|
|
91
91
|
LG_REDIRECT_URI_APP: 'app://eoh/sync-lg-device',
|
|
92
92
|
LG_URL: 'https://qt-vn.m.lgaccount.com/emp/v2',
|
|
93
|
+
VCONNEX_CLIENT_ID: '',
|
|
94
|
+
VCONNEX_REDIRECT_URI_APP: '',
|
|
93
95
|
pusherAppKey: '9a591ae4a764acc08714',
|
|
94
96
|
pusherAppCluster: 'ap1',
|
|
95
97
|
};
|
|
@@ -100,6 +102,8 @@ export class SCConfig {
|
|
|
100
102
|
static LG_CLIENT_ID = SCDefaultConfig.LG_CLIENT_ID;
|
|
101
103
|
static LG_REDIRECT_URI_APP = SCDefaultConfig.LG_REDIRECT_URI_APP;
|
|
102
104
|
static LG_URL = SCDefaultConfig.LG_URL;
|
|
105
|
+
static VCONNEX_CLIENT_ID = SCDefaultConfig.VCONNEX_CLIENT_ID;
|
|
106
|
+
static VCONNEX_REDIRECT_URI_APP = SCDefaultConfig.VCONNEX_REDIRECT_URI_APP;
|
|
103
107
|
static pusherAppKey = SCDefaultConfig.pusherAppKey;
|
|
104
108
|
static pusherAppCluste = SCDefaultConfig.pusherAppCluster;
|
|
105
109
|
}
|
|
@@ -113,6 +117,10 @@ export const initSCConfig = (config) => {
|
|
|
113
117
|
SCConfig.LG_REDIRECT_URI_APP =
|
|
114
118
|
config.LG_REDIRECT_URI_APP ?? SCDefaultConfig.LG_REDIRECT_URI_APP;
|
|
115
119
|
SCConfig.LG_URL = config.LG_URL ?? SCDefaultConfig.LG_URL;
|
|
120
|
+
SCConfig.VCONNEX_CLIENT_ID =
|
|
121
|
+
config.VCONNEX_CLIENT_ID ?? SCDefaultConfig.VCONNEX_CLIENT_ID;
|
|
122
|
+
SCConfig.VCONNEX_REDIRECT_URI_APP =
|
|
123
|
+
config.VCONNEX_REDIRECT_URI_APP ?? SCDefaultConfig.VCONNEX_REDIRECT_URI_APP;
|
|
116
124
|
SCConfig.pusherAppKey = config.pusherAppKey ?? SCDefaultConfig.pusherAppKey;
|
|
117
125
|
SCConfig.pusherAppCluster =
|
|
118
126
|
config.pusherAppCluster ?? SCDefaultConfig.pusherAppCluster;
|
|
@@ -5,6 +5,7 @@ import t from '../../hooks/Common/useTranslations';
|
|
|
5
5
|
import base64 from 'react-native-base64';
|
|
6
6
|
import { BleManager } from 'react-native-ble-plx';
|
|
7
7
|
import { ToastBottomHelper } from '../../utils/Utils';
|
|
8
|
+
import { useEffect, useState } from 'react';
|
|
8
9
|
|
|
9
10
|
const bluetoothDevices = {};
|
|
10
11
|
const needToScanDevices = [];
|
|
@@ -165,6 +166,19 @@ export const isBluetoothEnabled = async () => {
|
|
|
165
166
|
return state === 'PoweredOn';
|
|
166
167
|
};
|
|
167
168
|
|
|
169
|
+
export const useIsBluetoothEnabled = () => {
|
|
170
|
+
const [isEnabled, setIsEnabled] = useState(null);
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
const subscription = bleManager.onStateChange((state) => {
|
|
174
|
+
setIsEnabled(state === 'PoweredOn');
|
|
175
|
+
}, true);
|
|
176
|
+
|
|
177
|
+
return () => subscription.remove();
|
|
178
|
+
}, []);
|
|
179
|
+
return isEnabled;
|
|
180
|
+
};
|
|
181
|
+
|
|
168
182
|
export const enableBluetoothForAndroid = async () => {
|
|
169
183
|
await bleManager.enable();
|
|
170
184
|
};
|
|
@@ -11,6 +11,7 @@ import Routes from '../../utils/Route';
|
|
|
11
11
|
import { TESTID } from '../../configs/Constants';
|
|
12
12
|
import styles from './SelectSubUnitStyles';
|
|
13
13
|
import Button from '../../commons/Button';
|
|
14
|
+
import { SCConfig } from '../../configs/SCConfig';
|
|
14
15
|
|
|
15
16
|
const AddCommonSelectSubUnit = ({ route }) => {
|
|
16
17
|
const t = useTranslations();
|
|
@@ -31,6 +32,9 @@ const AddCommonSelectSubUnit = ({ route }) => {
|
|
|
31
32
|
case 'AddHassiDevice':
|
|
32
33
|
setTitle(t('select_a_sub_unit'));
|
|
33
34
|
break;
|
|
35
|
+
case 'AddVconnexDevice':
|
|
36
|
+
setTitle(t('select_a_sub_unit'));
|
|
37
|
+
break;
|
|
34
38
|
default:
|
|
35
39
|
setTitle(t('add_new_gateway'));
|
|
36
40
|
setSubTitle(t('select_a_sub_unit'));
|
|
@@ -70,10 +74,28 @@ const AddCommonSelectSubUnit = ({ route }) => {
|
|
|
70
74
|
station: subUnits[selectedIndex]?.id,
|
|
71
75
|
});
|
|
72
76
|
break;
|
|
77
|
+
case 'AddVconnexDevice':
|
|
78
|
+
navigation.navigate(Routes.Browser, {
|
|
79
|
+
link: API.IOT.VCONNEX.AUTHORIZE(
|
|
80
|
+
SCConfig.VCONNEX_CLIENT_ID,
|
|
81
|
+
SCConfig.VCONNEX_REDIRECT_URI_APP,
|
|
82
|
+
unit.user_id,
|
|
83
|
+
subUnits[selectedIndex]?.id
|
|
84
|
+
),
|
|
85
|
+
});
|
|
86
|
+
break;
|
|
73
87
|
default:
|
|
74
88
|
break;
|
|
75
89
|
}
|
|
76
|
-
}, [
|
|
90
|
+
}, [
|
|
91
|
+
addType,
|
|
92
|
+
navigation,
|
|
93
|
+
subUnits,
|
|
94
|
+
selectedIndex,
|
|
95
|
+
unit?.name,
|
|
96
|
+
unit.user_id,
|
|
97
|
+
route.params,
|
|
98
|
+
]);
|
|
77
99
|
|
|
78
100
|
const handleSelectIndex = (index) => {
|
|
79
101
|
if (index !== selectedIndex) {
|
|
@@ -48,6 +48,9 @@ const AddCommonSelectUnit = ({ route }) => {
|
|
|
48
48
|
case 'AddHassioDevice':
|
|
49
49
|
setTitle(t('text_select_a_unit'));
|
|
50
50
|
break;
|
|
51
|
+
case 'AddVconnexDevice':
|
|
52
|
+
setTitle(t('text_select_a_unit'));
|
|
53
|
+
break;
|
|
51
54
|
default:
|
|
52
55
|
setTitle(t('add_new_sub_unit'));
|
|
53
56
|
setSubTitle(t('add_new_subunit_select_unit'));
|
|
@@ -102,6 +105,15 @@ const AddCommonSelectUnit = ({ route }) => {
|
|
|
102
105
|
unit_id: units[selectedIndex].id,
|
|
103
106
|
});
|
|
104
107
|
break;
|
|
108
|
+
case 'AddVconnexDevice':
|
|
109
|
+
navigation.navigate(Routes.AddDeviceStack, {
|
|
110
|
+
screen: Routes.AddCommonSelectSubUnit,
|
|
111
|
+
params: {
|
|
112
|
+
unit_id: units[selectedIndex].id,
|
|
113
|
+
addType: 'AddVconnexDevice',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
105
117
|
default:
|
|
106
118
|
break;
|
|
107
119
|
}
|
|
@@ -239,11 +239,6 @@ describe('test DeviceDetail', () => {
|
|
|
239
239
|
);
|
|
240
240
|
expect(sensorDisplayItem.length).toEqual(2);
|
|
241
241
|
|
|
242
|
-
const itemMediaPlayer = instance.find(
|
|
243
|
-
(el) => el.props.testID === TESTID.DEVICE_DETAIL_MEDIA_PLAYER
|
|
244
|
-
);
|
|
245
|
-
expect(itemMediaPlayer).toBeDefined();
|
|
246
|
-
|
|
247
242
|
const itemActionGroup = instance.find(
|
|
248
243
|
(el) => el.props.testID === TESTID.DEVICE_DETAIL_ACTION_GROUP
|
|
249
244
|
);
|
|
@@ -2,11 +2,8 @@ import React, { useCallback } from 'react';
|
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import ActionGroup from '../../../commons/ActionGroup';
|
|
4
4
|
import { Card } from '../../../commons/CardShadow';
|
|
5
|
-
import MediaPlayer from '../../../commons/MediaPlayer';
|
|
6
|
-
import { Device } from '../../../configs';
|
|
7
5
|
import { TESTID } from '../../../configs/Constants';
|
|
8
6
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
9
|
-
import { standardizeCameraScreenSize } from '../../../utils/Utils';
|
|
10
7
|
import { DetailHistoryChart } from './DetailHistoryChart';
|
|
11
8
|
import { sendRemoteCommand } from '../../../iot/RemoteControl';
|
|
12
9
|
import CurrentRainSensor from '../../../commons/Device/RainningSensor/CurrentRainSensor';
|
|
@@ -20,8 +17,11 @@ import ListQualityIndicator from '../../../commons/Device/WaterQualitySensor/Lis
|
|
|
20
17
|
import EmergencyDetail from '../../../commons/Device/Emergency/EmergencyDetail';
|
|
21
18
|
import EmergencyButton from '../../../commons/Device/Emergency/EmergencyButton';
|
|
22
19
|
import FooterInfo from '../../../commons/Device/FooterInfo';
|
|
20
|
+
import MediaPlayerDetail from '../../../commons/MediaPlayerDetail';
|
|
21
|
+
import { standardizeCameraScreenSize } from '../../../utils/Utils';
|
|
22
|
+
import { Device } from '../../../configs';
|
|
23
23
|
|
|
24
|
-
const {
|
|
24
|
+
const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
|
|
25
25
|
Device.screenWidth - 32
|
|
26
26
|
);
|
|
27
27
|
|
|
@@ -51,15 +51,15 @@ export const SensorDisplayItem = ({
|
|
|
51
51
|
return (
|
|
52
52
|
<Card title={t('camera')}>
|
|
53
53
|
<View style={styles.mediaContainer}>
|
|
54
|
-
<
|
|
55
|
-
testID={TESTID.DEVICE_DETAIL_MEDIA_PLAYER}
|
|
54
|
+
<MediaPlayerDetail
|
|
56
55
|
uri={item.configuration.uri}
|
|
57
|
-
style={{ height: standardizeHeight }}
|
|
58
|
-
ratioWidth={standardizeWidth - 32}
|
|
59
56
|
thumbnail={{
|
|
60
|
-
uri:
|
|
57
|
+
uri: background,
|
|
61
58
|
}}
|
|
62
|
-
|
|
59
|
+
key={`camera-device-${item.configuration.id}`}
|
|
60
|
+
cameraName={item.configuration.name}
|
|
61
|
+
width={standardizeWidth - 32}
|
|
62
|
+
height={standardizeHeight - 16}
|
|
63
63
|
/>
|
|
64
64
|
</View>
|
|
65
65
|
</Card>
|
|
@@ -79,7 +79,21 @@ const DeviceDetail = ({ route }) => {
|
|
|
79
79
|
[sensor]
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
const isDeviceHasBle = useMemo(() => {
|
|
83
|
+
const action = display.items.filter((item) => item.type === 'action');
|
|
84
|
+
if (action.length === 0) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return action.some((item) => {
|
|
89
|
+
const { configuration } = item?.configuration;
|
|
90
|
+
return JSON.stringify(configuration).includes(
|
|
91
|
+
'"command_prefer_over_bluetooth":true'
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
}, [display]);
|
|
95
|
+
|
|
96
|
+
useDisconnectedDevice(sensorName, isDeviceHasBle);
|
|
83
97
|
|
|
84
98
|
const netInfo = useNetInfo();
|
|
85
99
|
|
|
@@ -4,10 +4,11 @@ import { Alert, Linking, Platform } from 'react-native';
|
|
|
4
4
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
5
5
|
import {
|
|
6
6
|
enableBluetoothForAndroid,
|
|
7
|
-
|
|
7
|
+
useIsBluetoothEnabled,
|
|
8
8
|
} from '../../../iot/RemoteControl/Bluetooth';
|
|
9
|
+
import { ToastBottomHelper } from '../../../utils/Utils';
|
|
9
10
|
|
|
10
|
-
export const useDisconnectedDevice = (
|
|
11
|
+
export const useDisconnectedDevice = (sensorName, isDeviceHasBle) => {
|
|
11
12
|
const t = useTranslations();
|
|
12
13
|
const openBluetoothIOS = () => {
|
|
13
14
|
Linking.openURL('App-Prefs:Bluetooth');
|
|
@@ -32,32 +33,36 @@ export const useDisconnectedDevice = (isConnected, sensorName) => {
|
|
|
32
33
|
onPress: () => enableBluetoothForAndroid(),
|
|
33
34
|
},
|
|
34
35
|
];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{ name: sensorName }
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
36
|
+
|
|
37
|
+
const netState = NetInfo.useNetInfo();
|
|
38
|
+
const isBluetoothEnabled = useIsBluetoothEnabled();
|
|
39
|
+
|
|
40
|
+
const checkNetWorkConnect = useCallback(
|
|
41
|
+
async (isHavingInternet, isBtEnabled) => {
|
|
42
|
+
if (!isHavingInternet && isDeviceHasBle) {
|
|
43
|
+
if (isBtEnabled === true) {
|
|
44
|
+
ToastBottomHelper.info(
|
|
45
|
+
t('your_internet_is_disconnected', { name: sensorName }),
|
|
46
|
+
t('change_to_control_via_bluetooth_connection', {
|
|
47
|
+
name: sensorName,
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
} else if (isBtEnabled === false) {
|
|
51
|
+
Alert.alert(
|
|
52
|
+
'',
|
|
53
|
+
t(
|
|
54
|
+
'your_connection_to_the_server_was_disconnected_please_open_the_bluetooth_to_continue'
|
|
55
|
+
),
|
|
56
|
+
actions
|
|
57
|
+
);
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
|
-
}
|
|
60
|
+
},
|
|
57
61
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
58
|
-
|
|
62
|
+
[isDeviceHasBle]
|
|
63
|
+
);
|
|
59
64
|
|
|
60
65
|
useEffect(() => {
|
|
61
|
-
checkNetWorkConnect();
|
|
62
|
-
}, [checkNetWorkConnect]);
|
|
66
|
+
checkNetWorkConnect(netState.isConnected, isBluetoothEnabled);
|
|
67
|
+
}, [netState.isConnected, isBluetoothEnabled, checkNetWorkConnect]);
|
|
63
68
|
};
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { IconOutline } from '@ant-design/icons-react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
|
-
import {
|
|
5
|
-
StyleSheet,
|
|
6
|
-
TouchableOpacity,
|
|
7
|
-
View,
|
|
8
|
-
ActivityIndicator,
|
|
9
|
-
} from 'react-native';
|
|
4
|
+
import { StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
10
5
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
11
6
|
|
|
12
7
|
import { Colors } from '../../configs';
|
|
@@ -91,9 +86,7 @@ const MemberList = ({ route }) => {
|
|
|
91
86
|
loading={isRefresh}
|
|
92
87
|
onRefresh={onRefresh}
|
|
93
88
|
>
|
|
94
|
-
{loading
|
|
95
|
-
<ActivityIndicator />
|
|
96
|
-
) : (
|
|
89
|
+
{!loading && (
|
|
97
90
|
<SharingMembers
|
|
98
91
|
dataMember={dataMembers}
|
|
99
92
|
ownerId={unit.user_id}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
|
-
import { StyleSheet,
|
|
2
|
+
import { StyleSheet, View, ScrollView } from 'react-native';
|
|
3
3
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
4
4
|
import { TESTID } from '../../configs/Constants';
|
|
5
5
|
|
|
@@ -12,8 +12,8 @@ const TDSGuide = memo(() => {
|
|
|
12
12
|
useTitleHeader(t('tds_infomation'));
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<
|
|
16
|
-
<ScrollView>
|
|
15
|
+
<View style={styles.container}>
|
|
16
|
+
<ScrollView style={styles.paddingHorizontal16}>
|
|
17
17
|
<Text
|
|
18
18
|
type="H3"
|
|
19
19
|
semibold
|
|
@@ -38,7 +38,7 @@ const TDSGuide = memo(() => {
|
|
|
38
38
|
</Text>
|
|
39
39
|
</>
|
|
40
40
|
</ScrollView>
|
|
41
|
-
</
|
|
41
|
+
</View>
|
|
42
42
|
);
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -48,6 +48,8 @@ const styles = StyleSheet.create({
|
|
|
48
48
|
container: {
|
|
49
49
|
flex: 1,
|
|
50
50
|
backgroundColor: Theme.color.backgroundColor,
|
|
51
|
+
},
|
|
52
|
+
paddingHorizontal16: {
|
|
51
53
|
paddingHorizontal: 16,
|
|
52
54
|
},
|
|
53
55
|
titlePadding: {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { create } from 'react-test-renderer';
|
|
3
|
+
import { act } from 'react-test-renderer';
|
|
4
|
+
import { Section } from '../../../commons';
|
|
5
|
+
import { SCProvider } from '../../../context';
|
|
6
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
7
|
+
import ManageUnit from './';
|
|
8
|
+
|
|
9
|
+
const wrapComponent = (route) => (
|
|
10
|
+
<SCProvider initState={mockSCStore({})}>
|
|
11
|
+
<ManageUnit route={route} />
|
|
12
|
+
</SCProvider>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
describe('Test ManageUnit', () => {
|
|
16
|
+
let tree;
|
|
17
|
+
const route = {
|
|
18
|
+
params: {
|
|
19
|
+
unit: {
|
|
20
|
+
user_id: 1,
|
|
21
|
+
name: 'test',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
it('Test render', async () => {
|
|
27
|
+
await act(() => {
|
|
28
|
+
tree = create(wrapComponent(route));
|
|
29
|
+
});
|
|
30
|
+
const instance = tree.root;
|
|
31
|
+
const Sections = instance.findAllByType(Section);
|
|
32
|
+
expect(Sections).toHaveLength(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/src/utils/Apis/axios.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { deleteData, getData, storeData } from '../Storage';
|
|
3
3
|
import { ToastBottomHelper } from '../Utils';
|
|
4
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
4
5
|
|
|
5
6
|
export const replaceParams = (apiURL, params) => {
|
|
6
7
|
let _result = apiURL;
|
|
@@ -12,7 +13,7 @@ export const replaceParams = (apiURL, params) => {
|
|
|
12
13
|
return _result;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
|
-
const parseErrorResponse = (error) => {
|
|
16
|
+
const parseErrorResponse = async (error) => {
|
|
16
17
|
let message;
|
|
17
18
|
let data = {};
|
|
18
19
|
if (
|
|
@@ -31,7 +32,18 @@ const parseErrorResponse = (error) => {
|
|
|
31
32
|
message = error.message;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
typeof message === 'string'
|
|
35
|
+
if (typeof message === 'string') {
|
|
36
|
+
let hideError = false;
|
|
37
|
+
if (message === 'Network Error') {
|
|
38
|
+
const netState = await NetInfo.fetch();
|
|
39
|
+
if (!netState.isConnected) {
|
|
40
|
+
hideError = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!hideError) {
|
|
44
|
+
ToastBottomHelper.error(message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
35
47
|
|
|
36
48
|
return {
|
|
37
49
|
success: false,
|
|
@@ -77,12 +89,12 @@ export async function axiosGet(URL, config = {}, cache = false) {
|
|
|
77
89
|
if (cache) {
|
|
78
90
|
// only network error or server error
|
|
79
91
|
if (!error.response || error.response.status >= 500) {
|
|
80
|
-
return (await axiosCache(URL)) || parseErrorResponse(error);
|
|
92
|
+
return (await axiosCache(URL)) || (await parseErrorResponse(error));
|
|
81
93
|
} else {
|
|
82
94
|
await deleteData(cacheKey);
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
|
-
return parseErrorResponse(error);
|
|
97
|
+
return await parseErrorResponse(error);
|
|
86
98
|
}
|
|
87
99
|
const { data } = response;
|
|
88
100
|
if (response.status === 200) {
|
|
@@ -106,7 +118,7 @@ async function axiosCall(method, ...args) {
|
|
|
106
118
|
try {
|
|
107
119
|
response = await axios[method](...args);
|
|
108
120
|
} catch (error) {
|
|
109
|
-
return parseErrorResponse(error);
|
|
121
|
+
return await parseErrorResponse(error);
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
const { data } = response;
|
|
@@ -826,7 +826,8 @@
|
|
|
826
826
|
"quick_button_create_at_dashboard": "Quick button create at dashboard",
|
|
827
827
|
"every_day_at": "Every day at {time}",
|
|
828
828
|
"you_do_not_have_the_device_or_have_share_control_device": "You do not have the device or you haven’t been shared the control for the device.",
|
|
829
|
-
"
|
|
829
|
+
"your_internet_is_disconnected": "Your internet is disconnected",
|
|
830
|
+
"change_to_control_via_bluetooth_connection": "change to control {name} via Bluetooth connection",
|
|
830
831
|
"your_connection_to_the_server_was_disconnected_please_open_the_bluetooth_to_continue": "Your connection to the server was disconnected, Please open the Bluetooth to continue",
|
|
831
832
|
"error_please_try_later": "Error! Please try later",
|
|
832
833
|
"mon": "Mon",
|
|
@@ -827,7 +827,8 @@
|
|
|
827
827
|
"quick_button_create_at_dashboard": "Tạo nút nhanh trên bảng điều khiển",
|
|
828
828
|
"every_day_at": "Mỗi ngày vào lúc {time}",
|
|
829
829
|
"you_do_not_have_the_device_or_have_share_control_device": "Bạn không có thiết bị nào hoặc chưa được cấp quyền để điều khiển thiết bị.",
|
|
830
|
-
"
|
|
830
|
+
"your_internet_is_disconnected": "Internet của bạn bị ngắt kết nối",
|
|
831
|
+
"change_to_control_via_bluetooth_connection": "thay đổi sang điều khiển {name} qua kết nối Bluetooth",
|
|
831
832
|
"your_connection_to_the_server_was_disconnected_please_open_the_bluetooth_to_continue": "Kết nối của bạn với máy chủ đã bị ngắt, vui lòng mở Bluetooth để tiếp tục",
|
|
832
833
|
"error_please_try_later": "Lỗi! Vui lòng thử lại sau",
|
|
833
834
|
"mon": "T2",
|
package/src/utils/Utils.js
CHANGED
|
@@ -26,6 +26,11 @@ export const isObjectEmpty = (obj) => {
|
|
|
26
26
|
return Object.keys(obj).length === 0;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
export const shortEmailName = (email) => {
|
|
30
|
+
const regex = '([^@]+)';
|
|
31
|
+
return email?.match(regex)[0];
|
|
32
|
+
};
|
|
33
|
+
|
|
29
34
|
export const formatNumberCompact = (number) => {
|
|
30
35
|
return new Intl.NumberFormat('en-US', {
|
|
31
36
|
notation: 'compact',
|
|
@@ -66,7 +71,7 @@ export const openMapDirection = (item) => () => {
|
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
export const ToastBottomHelper = {
|
|
69
|
-
success: (msg) => {
|
|
74
|
+
success: (msg, line2) => {
|
|
70
75
|
if (!Toast._ref) {
|
|
71
76
|
return;
|
|
72
77
|
}
|
|
@@ -75,10 +80,11 @@ export const ToastBottomHelper = {
|
|
|
75
80
|
type: 'success',
|
|
76
81
|
position: 'bottom',
|
|
77
82
|
text1: msg,
|
|
83
|
+
text2: line2,
|
|
78
84
|
visibilityTime: 1000,
|
|
79
85
|
});
|
|
80
86
|
},
|
|
81
|
-
error: (msg) => {
|
|
87
|
+
error: (msg, line2) => {
|
|
82
88
|
if (!Toast._ref) {
|
|
83
89
|
return;
|
|
84
90
|
}
|
|
@@ -87,6 +93,20 @@ export const ToastBottomHelper = {
|
|
|
87
93
|
type: 'error',
|
|
88
94
|
position: 'bottom',
|
|
89
95
|
text1: msg,
|
|
96
|
+
text2: line2,
|
|
97
|
+
visibilityTime: 1000,
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
info: (msg, line2) => {
|
|
101
|
+
if (!Toast._ref) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Toast.show({
|
|
106
|
+
type: 'info',
|
|
107
|
+
position: 'bottom',
|
|
108
|
+
text1: msg,
|
|
109
|
+
text2: line2,
|
|
90
110
|
visibilityTime: 1000,
|
|
91
111
|
});
|
|
92
112
|
},
|