@eohjsc/react-native-smart-city 0.2.92 → 0.2.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/Device/current-state.svg +3 -0
- package/assets/images/Device/door-state.svg +3 -0
- package/assets/images/Device/wind-strength.svg +12 -0
- package/package.json +1 -1
- package/src/commons/Action/ItemQuickAction.js +1 -0
- package/src/commons/Action/__test__/ItemQuickAction.test.js +49 -2
- package/src/commons/ActionGroup/OnOffTemplate/OnOffSimpleTemplateStyle.js +2 -1
- package/src/commons/ActionGroup/OptionsDropdownActionTemplate.js +31 -11
- package/src/commons/ActionGroup/OptionsDropdownActionTemplateStyle.js +5 -2
- package/src/commons/ActionGroup/TimerActionTemplate.js +14 -10
- package/src/commons/ActionGroup/TimerActionTemplateStyles.js +12 -0
- package/src/commons/ActionGroup/TwoButtonTemplate/TwoButtonTemplateStyles.js +55 -0
- package/src/commons/ActionGroup/TwoButtonTemplate/index.js +170 -0
- package/src/commons/ActionGroup/__test__/TimerActionTemplate.test.js +1 -1
- package/src/commons/ActionGroup/__test__/TwoButtonTemplate.test.js +112 -0
- package/src/commons/ActionGroup/index.js +3 -0
- package/src/commons/CameraDevice/index.js +6 -1
- package/src/commons/Device/HistoryChart.js +2 -2
- package/src/commons/Device/ItemDevice.js +3 -13
- package/src/commons/IconComponent/index.js +32 -26
- package/src/commons/MediaPlayerDetail/index.js +16 -4
- package/src/commons/SubUnit/Favorites/index.js +8 -7
- package/src/commons/SubUnit/__test__/Favorites.test.js +33 -35
- package/src/configs/API.js +4 -0
- package/src/configs/Constants.js +21 -0
- package/src/context/actionType.ts +17 -0
- package/src/context/mockStore.ts +18 -0
- package/src/context/reducer.ts +102 -0
- package/src/iot/RemoteControl/Bluetooth.js +2 -0
- package/src/iot/RemoteControl/GoogleHome.js +1 -0
- package/src/navigations/AutomateStack.js +16 -1
- package/src/navigations/UnitStack.js +27 -0
- package/src/screens/AddNewAction/Device/__test__/index.test.js +1 -1
- package/src/screens/AddNewAction/SelectAction.js +13 -15
- package/src/screens/AddNewAction/__test__/SelectAction.test.js +0 -7
- package/src/screens/AddNewGateway/PlugAndPlay/ConnectWifiWarning.js +2 -0
- package/src/screens/AddNewGateway/PlugAndPlay/GatewayWifiList.js +2 -0
- package/src/screens/AddNewGateway/PlugAndPlay/__test__/ConnectWifiWarning.test.js +9 -0
- package/src/screens/AddNewGateway/PlugAndPlay/__test__/GatewayWifiList.test.js +15 -0
- package/src/screens/AddNewGateway/SetupGatewayWifi.js +6 -1
- package/src/screens/AddNewGateway/__test__/SetupGateway.test.js +34 -0
- package/src/screens/AllCamera/index.js +1 -0
- package/src/screens/Automate/MultiUnits.js +9 -9
- package/src/screens/Automate/index.js +21 -20
- package/src/screens/Device/__test__/detail.test.js +119 -86
- package/src/screens/Device/detail.js +38 -51
- package/src/screens/Device/hooks/useFavoriteDevice.js +38 -0
- package/src/screens/EmergencyContacts/EmergencyContactsList.js +1 -1
- package/src/screens/EmergencyContacts/EmergencyContactsSelectContacts.js +41 -44
- package/src/screens/EmergencyContacts/__test__/EmergencyContactList.test.js +1 -0
- package/src/screens/EmergencyContacts/__test__/EmergencyContactsSelectContacts.test.js +18 -19
- package/src/screens/Notification/__test__/NotificationItem.test.js +64 -53
- package/src/screens/Notification/components/NotificationItem.js +13 -4
- package/src/screens/ScriptDetail/__test__/index.test.js +15 -4
- package/src/screens/ScriptDetail/hooks/useStarredScript.js +32 -0
- package/src/screens/ScriptDetail/index.js +11 -20
- package/src/screens/SharedUnit/__test__/TabHeader.test.js +5 -0
- package/src/screens/Sharing/SelectUser.js +3 -23
- package/src/screens/Sharing/__test__/SelectUser.test.js +12 -80
- package/src/screens/SmartIr/__test__/GroupButtonByType.test.js +33 -0
- package/src/screens/SmartIr/components/GroupButtonByType/GroupButtonByType.js +2 -0
- package/src/screens/Unit/ChooseLocation.js +5 -0
- package/src/screens/Unit/Detail.js +33 -37
- package/src/screens/Unit/ManageUnit.js +21 -20
- package/src/screens/Unit/ManageUnitStyles.js +1 -0
- package/src/screens/Unit/SelectAddress.js +8 -2
- package/src/screens/Unit/Summaries.js +12 -15
- package/src/screens/Unit/__test__/Detail.test.js +25 -0
- package/src/screens/Unit/components/__test__/Header.test.js +32 -0
- package/src/screens/Unit/hook/useFavorites.js +28 -0
- package/src/utils/Apis/axios.js +7 -2
- package/src/utils/I18n/translations/en.json +1 -4
- package/src/utils/I18n/translations/vi.json +1 -4
|
@@ -11,7 +11,6 @@ import { API } from '../../../configs';
|
|
|
11
11
|
import { getTranslate } from '../../../utils/I18n';
|
|
12
12
|
import { SCProvider } from '../../../context';
|
|
13
13
|
import { mockSCStore } from '../../../context/mockStore';
|
|
14
|
-
import { ToastBottomHelper } from '../../../utils/Utils';
|
|
15
14
|
|
|
16
15
|
const wrapComponent = (route) => (
|
|
17
16
|
<SCProvider initState={mockSCStore({})}>
|
|
@@ -53,6 +52,7 @@ describe('test SelectUser container', () => {
|
|
|
53
52
|
let route;
|
|
54
53
|
|
|
55
54
|
beforeEach(() => {
|
|
55
|
+
axios.post.mockClear();
|
|
56
56
|
route = {
|
|
57
57
|
params: {
|
|
58
58
|
unit: {
|
|
@@ -66,10 +66,6 @@ describe('test SelectUser container', () => {
|
|
|
66
66
|
};
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
afterEach(() => {
|
|
70
|
-
axios.post.mockClear();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
69
|
const findByTestId = (instance, id) => {
|
|
74
70
|
return instance.find((el) => el.props.testID === id);
|
|
75
71
|
};
|
|
@@ -158,8 +154,16 @@ describe('test SelectUser container', () => {
|
|
|
158
154
|
expect(axios.post).not.toHaveBeenCalled();
|
|
159
155
|
});
|
|
160
156
|
|
|
161
|
-
test('_TextInput onChange phone,
|
|
162
|
-
const response = {
|
|
157
|
+
test('_TextInput onChange phone, validated and call api sharedPermission', async () => {
|
|
158
|
+
const response = {
|
|
159
|
+
status: 200,
|
|
160
|
+
data: {
|
|
161
|
+
user: {
|
|
162
|
+
id: 2,
|
|
163
|
+
name: 'user add',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
163
167
|
mockAxiosPost(response);
|
|
164
168
|
|
|
165
169
|
await act(async () => {
|
|
@@ -190,78 +194,6 @@ describe('test SelectUser container', () => {
|
|
|
190
194
|
});
|
|
191
195
|
|
|
192
196
|
accountList = instance.findAllByType(AccountList);
|
|
193
|
-
expect(accountList).toHaveLength(
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test('_TextInput onChange unitOwner phone, validate and call api sharedPermission', async () => {
|
|
197
|
-
const response = {
|
|
198
|
-
status: 400,
|
|
199
|
-
data: { non_field_errors: 'You cannot invite yourself' },
|
|
200
|
-
};
|
|
201
|
-
mockAxiosPost(response);
|
|
202
|
-
|
|
203
|
-
const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
|
|
204
|
-
|
|
205
|
-
await act(async () => {
|
|
206
|
-
tree = create(wrapComponent(route));
|
|
207
|
-
});
|
|
208
|
-
const instance = tree.root;
|
|
209
|
-
|
|
210
|
-
const textInput = instance.findByType(_TextInput);
|
|
211
|
-
const button = instance.findByType(Button);
|
|
212
|
-
|
|
213
|
-
await act(async () => {
|
|
214
|
-
await textInput.props.onChange('0909123456');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
await act(async () => {
|
|
218
|
-
await button.props.onPress();
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
expect(textInput.props.errorText).toEqual('');
|
|
222
|
-
expect(axios.post).toHaveBeenCalledWith(API.SHARE.SHARE(), {
|
|
223
|
-
phone: '0909123456',
|
|
224
|
-
email: '',
|
|
225
|
-
unit: 1,
|
|
226
|
-
permissions: { controlPermissions: {}, readPermissions: {} },
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
expect(spyToastError).toBeCalled();
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('_TextInput onChange phone, validate and call api not sharedPermission', async () => {
|
|
233
|
-
const response = {
|
|
234
|
-
status: 400,
|
|
235
|
-
data: { phone: '0909123456' },
|
|
236
|
-
};
|
|
237
|
-
mockAxiosPost(response);
|
|
238
|
-
|
|
239
|
-
const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
|
|
240
|
-
|
|
241
|
-
await act(async () => {
|
|
242
|
-
tree = create(wrapComponent(route));
|
|
243
|
-
});
|
|
244
|
-
const instance = tree.root;
|
|
245
|
-
|
|
246
|
-
const textInput = instance.findByType(_TextInput);
|
|
247
|
-
const button = instance.findByType(Button);
|
|
248
|
-
|
|
249
|
-
await act(async () => {
|
|
250
|
-
await textInput.props.onChange('0909123456');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
await act(async () => {
|
|
254
|
-
await button.props.onPress();
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
expect(textInput.props.errorText).toEqual('');
|
|
258
|
-
expect(axios.post).toHaveBeenCalledWith(API.SHARE.SHARE(), {
|
|
259
|
-
phone: '0909123456',
|
|
260
|
-
email: '',
|
|
261
|
-
unit: 1,
|
|
262
|
-
permissions: { controlPermissions: {}, readPermissions: {} },
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
expect(spyToastError).toBeCalled();
|
|
197
|
+
expect(accountList).toHaveLength(1);
|
|
266
198
|
});
|
|
267
199
|
});
|
|
@@ -5,6 +5,8 @@ import { SCProvider } from '../../../context';
|
|
|
5
5
|
import { mockSCStore } from '../../../context/mockStore';
|
|
6
6
|
import WrapHeaderScrollable from '../../../commons/Sharing/WrapHeaderScrollable';
|
|
7
7
|
import { ButtonsBottom } from '../components/GroupButtonByType/ButtonsBottom';
|
|
8
|
+
import { TESTID } from '../../../configs/Constants';
|
|
9
|
+
import TextInput from '../../../commons/Form/TextInput';
|
|
8
10
|
|
|
9
11
|
const wrapComponent = (route) => (
|
|
10
12
|
<SCProvider initState={mockSCStore({})}>
|
|
@@ -42,6 +44,18 @@ describe('Test GroupButtonByType', () => {
|
|
|
42
44
|
const instance = tree.root;
|
|
43
45
|
const wrapHeaderScrollable = instance.findAllByType(WrapHeaderScrollable);
|
|
44
46
|
expect(wrapHeaderScrollable).toHaveLength(1);
|
|
47
|
+
|
|
48
|
+
const ButtonBottom = instance.find(
|
|
49
|
+
(el) => el.props.testID === TESTID.GROUP_BUTTON_TYPE.BUTTON_BOTTOM
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
act(() => {
|
|
53
|
+
ButtonBottom.props.onPressLeft();
|
|
54
|
+
});
|
|
55
|
+
act(() => {
|
|
56
|
+
ButtonBottom.props.onPressRight();
|
|
57
|
+
});
|
|
58
|
+
expect(ButtonBottom).toBeDefined();
|
|
45
59
|
});
|
|
46
60
|
test('onPressLeft ButtonsBottom', () => {
|
|
47
61
|
const route = {
|
|
@@ -77,4 +91,23 @@ describe('Test GroupButtonByType', () => {
|
|
|
77
91
|
buttonsBottom[0].props.onPressRight();
|
|
78
92
|
});
|
|
79
93
|
});
|
|
94
|
+
|
|
95
|
+
test('onChange TextInputPassword', async () => {
|
|
96
|
+
const route = {
|
|
97
|
+
params: {
|
|
98
|
+
device_type: { id: 1, name: '', icon: '' },
|
|
99
|
+
brand: { id: 1, name: '' },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
act(() => {
|
|
103
|
+
tree = renderer.create(wrapComponent(route));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const instance = tree.root;
|
|
107
|
+
const textInput = instance.findAllByType(TextInput);
|
|
108
|
+
await act(async () => {
|
|
109
|
+
await textInput[0].props.onChange();
|
|
110
|
+
});
|
|
111
|
+
expect(textInput).toHaveLength(1);
|
|
112
|
+
});
|
|
80
113
|
});
|
|
@@ -13,6 +13,7 @@ import { ButtonsBottom } from './ButtonsBottom';
|
|
|
13
13
|
import { Remote, SmartIr, Union } from '../../../../Images/SmartIr';
|
|
14
14
|
|
|
15
15
|
import styles from './GroupButtonByTypeStyles';
|
|
16
|
+
import { TESTID } from '../../../../configs/Constants';
|
|
16
17
|
|
|
17
18
|
const GroupButtonByType = memo(({ route }) => {
|
|
18
19
|
const t = useTranslations();
|
|
@@ -141,6 +142,7 @@ const GroupButtonByType = memo(({ route }) => {
|
|
|
141
142
|
selectionColor={Colors.Primary}
|
|
142
143
|
/>
|
|
143
144
|
<ButtonsBottom
|
|
145
|
+
testID={TESTID.GROUP_BUTTON_TYPE.BUTTON_BOTTOM}
|
|
144
146
|
onPressLeft={cancelButton}
|
|
145
147
|
onPressRight={ApplyButton}
|
|
146
148
|
textLeft={t('cancel')}
|
|
@@ -5,6 +5,7 @@ import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
|
|
|
5
5
|
import { useNavigation } from '@react-navigation/native';
|
|
6
6
|
import MarkerGeolocation from '../../../assets/images/Map/MarkerGeolocation.svg';
|
|
7
7
|
import BottomButtonView from '../../commons/BottomButtonView';
|
|
8
|
+
import { FullLoading } from '../../commons';
|
|
8
9
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
9
10
|
|
|
10
11
|
navigator.geolocation = require('@react-native-community/geolocation');
|
|
@@ -27,6 +28,7 @@ const ChooseLocation = memo(({ route }) => {
|
|
|
27
28
|
const { location, setAddress, setLocation } = route.params;
|
|
28
29
|
const { goBack } = useNavigation();
|
|
29
30
|
const [currentLocation, setCurrentLocation] = useState(location);
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
30
32
|
const mapRef = useRef(null);
|
|
31
33
|
|
|
32
34
|
const onDone = useCallback(async () => {
|
|
@@ -34,6 +36,7 @@ const ChooseLocation = memo(({ route }) => {
|
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
36
38
|
const { latitude, longitude } = currentLocation;
|
|
39
|
+
setLoading(true);
|
|
37
40
|
const { success, data } = await axiosGet(
|
|
38
41
|
API.EXTERNAL.GOOGLE_MAP.GET_LOCATION_FROM_LAT_LNG,
|
|
39
42
|
{
|
|
@@ -53,6 +56,7 @@ const ChooseLocation = memo(({ route }) => {
|
|
|
53
56
|
});
|
|
54
57
|
goBack();
|
|
55
58
|
}
|
|
59
|
+
setLoading(false);
|
|
56
60
|
}, [currentLocation, setAddress, setLocation, goBack]);
|
|
57
61
|
|
|
58
62
|
const onRegionChange = useCallback(
|
|
@@ -89,6 +93,7 @@ const ChooseLocation = memo(({ route }) => {
|
|
|
89
93
|
onPressMain={onDone}
|
|
90
94
|
typeMain="primaryText"
|
|
91
95
|
/>
|
|
96
|
+
{loading && <FullLoading />}
|
|
92
97
|
</View>
|
|
93
98
|
);
|
|
94
99
|
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
useRef,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { AppState, RefreshControl, View } from 'react-native';
|
|
3
9
|
import { useIsFocused } from '@react-navigation/native';
|
|
4
10
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
@@ -16,6 +22,7 @@ import {
|
|
|
16
22
|
useIsOwnerOfUnit,
|
|
17
23
|
usePopover,
|
|
18
24
|
} from '../../hooks/Common';
|
|
25
|
+
import { useFavorites } from './hook/useFavorites';
|
|
19
26
|
import { scanBluetoothDevices } from '../../iot/RemoteControl/Bluetooth';
|
|
20
27
|
import { googleHomeConnect } from '../../iot/RemoteControl/GoogleHome';
|
|
21
28
|
import { axiosPost, fetchWithCache } from '../../utils/Apis/axios';
|
|
@@ -31,7 +38,11 @@ import { useNavigation } from '@react-navigation/native';
|
|
|
31
38
|
import Routes from '../../utils/Route';
|
|
32
39
|
import SubUnitAutomate from '../../commons/SubUnit/OneTap';
|
|
33
40
|
import SubUnitFavorites from '../../commons/SubUnit/Favorites';
|
|
34
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
AUTOMATE_TYPE,
|
|
43
|
+
NOTIFICATION_TYPES,
|
|
44
|
+
TESTID,
|
|
45
|
+
} from '../../configs/Constants';
|
|
35
46
|
import {
|
|
36
47
|
watchNotificationData,
|
|
37
48
|
unwatchNotificationData,
|
|
@@ -66,26 +77,27 @@ const UnitDetail = ({ route }) => {
|
|
|
66
77
|
);
|
|
67
78
|
|
|
68
79
|
const [unit, setUnit] = useState(unitData || { id: unitId });
|
|
69
|
-
const [appState, setAppState] = useState(AppState.currentState);
|
|
70
80
|
const [listMenuItem, setListMenuItem] = useState([]);
|
|
71
81
|
const [listStation, setListStation] = useState([]);
|
|
72
82
|
const [listAutomate, setListAutomate] = useState([]);
|
|
73
|
-
const [favorites, setFavorites] = useState({
|
|
74
|
-
devices: [],
|
|
75
|
-
automates: [],
|
|
76
|
-
});
|
|
77
83
|
const [isGGHomeConnected, setIsGGHomeConnected] = useState(false);
|
|
78
84
|
const [station, setStation] = useState({});
|
|
79
85
|
const [indexStation, setIndexStation] = useState(0);
|
|
80
86
|
const [showAdd, setShowAdd, setHideAdd] = useBoolean();
|
|
81
87
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
82
88
|
const [dataFullScreen, setDataFullScreen] = useState();
|
|
89
|
+
const appState = useRef(AppState.currentState);
|
|
83
90
|
|
|
84
91
|
const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
|
|
85
92
|
usePopover();
|
|
86
93
|
|
|
87
94
|
const { isOwner } = useIsOwnerOfUnit(unit.user_id);
|
|
88
95
|
|
|
96
|
+
const { favoriteDevices, favoriteAutomates } = useFavorites(
|
|
97
|
+
unit.stations || [],
|
|
98
|
+
listAutomate
|
|
99
|
+
);
|
|
100
|
+
|
|
89
101
|
const handleFullScreen = (data) => {
|
|
90
102
|
setIsFullScreen(!isFullScreen);
|
|
91
103
|
setDataFullScreen(data);
|
|
@@ -105,18 +117,6 @@ const UnitDetail = ({ route }) => {
|
|
|
105
117
|
isFavorites: true,
|
|
106
118
|
name: t('favorites'),
|
|
107
119
|
});
|
|
108
|
-
let favoriteDevices = [];
|
|
109
|
-
rawUnitData.stations.forEach((stationItem) => {
|
|
110
|
-
if (stationItem.sensors) {
|
|
111
|
-
favoriteDevices = favoriteDevices.concat(
|
|
112
|
-
stationItem.sensors.filter((sensorItem) => sensorItem.is_favourite)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
setFavorites((prevData) => ({
|
|
117
|
-
...prevData,
|
|
118
|
-
devices: favoriteDevices,
|
|
119
|
-
}));
|
|
120
120
|
},
|
|
121
121
|
[t]
|
|
122
122
|
);
|
|
@@ -153,10 +153,6 @@ const UnitDetail = ({ route }) => {
|
|
|
153
153
|
type: AUTOMATE_TYPE.AUTOMATION,
|
|
154
154
|
},
|
|
155
155
|
]);
|
|
156
|
-
setFavorites((prevData) => ({
|
|
157
|
-
...prevData,
|
|
158
|
-
automates: data.filter((item) => item?.script?.is_star),
|
|
159
|
-
}));
|
|
160
156
|
}
|
|
161
157
|
}
|
|
162
158
|
);
|
|
@@ -170,23 +166,21 @@ const UnitDetail = ({ route }) => {
|
|
|
170
166
|
[fetchDetails]
|
|
171
167
|
);
|
|
172
168
|
|
|
173
|
-
|
|
174
|
-
(nextAppState) => {
|
|
175
|
-
if (
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
const subscription = AppState.addEventListener('change', (nextAppState) => {
|
|
171
|
+
if (
|
|
172
|
+
appState.current.match(/inactive|background/) &&
|
|
173
|
+
nextAppState === 'active'
|
|
174
|
+
) {
|
|
176
175
|
fetchDetails();
|
|
177
176
|
}
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
[appState, fetchDetails]
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
AppState.addEventListener('change', handleAppStateChange);
|
|
177
|
+
appState.current = nextAppState;
|
|
178
|
+
});
|
|
185
179
|
|
|
186
180
|
return () => {
|
|
187
|
-
|
|
181
|
+
subscription.remove();
|
|
188
182
|
};
|
|
189
|
-
}, [
|
|
183
|
+
}, [fetchDetails]);
|
|
190
184
|
|
|
191
185
|
const handleGoogleHomeConnect = useCallback(
|
|
192
186
|
async (options) => {
|
|
@@ -299,9 +293,10 @@ const UnitDetail = ({ route }) => {
|
|
|
299
293
|
if (station?.isFavorites) {
|
|
300
294
|
return (
|
|
301
295
|
<SubUnitFavorites
|
|
302
|
-
unit={unit}
|
|
303
296
|
isOwner={isOwner}
|
|
304
|
-
|
|
297
|
+
unit={unit}
|
|
298
|
+
favoriteDevices={favoriteDevices}
|
|
299
|
+
favoriteAutomates={favoriteAutomates}
|
|
305
300
|
wrapItemStyle={styles.wrapItemStyle}
|
|
306
301
|
isGGHomeConnected={isGGHomeConnected}
|
|
307
302
|
/>
|
|
@@ -365,6 +360,7 @@ const UnitDetail = ({ route }) => {
|
|
|
365
360
|
<View style={styles.container}>
|
|
366
361
|
<Summaries unit={unit} />
|
|
367
362
|
<NavBar
|
|
363
|
+
testID={TESTID.NAVBAR_ON_SNAP_ITEM}
|
|
368
364
|
listStation={listStation}
|
|
369
365
|
listMenuItem={listMenuItem}
|
|
370
366
|
onSnapToItem={onSnapToItem}
|
|
@@ -39,25 +39,25 @@ const ButtonWrapper = ({
|
|
|
39
39
|
style={styles.buttonWrapper}
|
|
40
40
|
>
|
|
41
41
|
<View style={styles.buttonInfo}>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</Text>
|
|
46
|
-
) : (
|
|
47
|
-
(icon && <Image source={{ uri: icon }} style={styles.avatar} />) || (
|
|
48
|
-
<View style={styles.avatar}>
|
|
49
|
-
<Icon name={'user'} size={27} />
|
|
50
|
-
</View>
|
|
51
|
-
)
|
|
52
|
-
)}
|
|
42
|
+
<Text type="H4" semibold>
|
|
43
|
+
{title}
|
|
44
|
+
</Text>
|
|
53
45
|
<View style={styles.buttonValue}>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
{!icon ? (
|
|
47
|
+
<Text
|
|
48
|
+
type="Body"
|
|
49
|
+
color={valueColor || Colors.Gray7}
|
|
50
|
+
style={styles.value}
|
|
51
|
+
>
|
|
52
|
+
{value}
|
|
53
|
+
</Text>
|
|
54
|
+
) : (
|
|
55
|
+
<Image source={{ uri: value }} style={styles.avatar} /> || (
|
|
56
|
+
<View style={styles.avatar}>
|
|
57
|
+
<Icon name={'user'} size={27} />
|
|
58
|
+
</View>
|
|
59
|
+
)
|
|
60
|
+
)}
|
|
61
61
|
<IconOutline name="right" size={20} color={Colors.Gray7} />
|
|
62
62
|
</View>
|
|
63
63
|
</View>
|
|
@@ -218,8 +218,9 @@ const ManageUnit = ({ route }) => {
|
|
|
218
218
|
<>
|
|
219
219
|
<ButtonWrapper
|
|
220
220
|
onPress={() => handleChoosePhoto('avatar')}
|
|
221
|
-
value={
|
|
222
|
-
icon
|
|
221
|
+
value={unitData.icon}
|
|
222
|
+
icon
|
|
223
|
+
title={t('icon_unit')}
|
|
223
224
|
testID={TESTID.MANAGE_UNIT_CHANGE_PHOTO}
|
|
224
225
|
/>
|
|
225
226
|
<ButtonWrapper
|
|
@@ -9,6 +9,7 @@ import BottomButtonView from '../../commons/BottomButtonView';
|
|
|
9
9
|
import SearchBarLocation from '../../commons/SearchLocation';
|
|
10
10
|
import RowLocation from '../../commons/SearchLocation/RowLocation';
|
|
11
11
|
import Text from '../../commons/Text';
|
|
12
|
+
import { FullLoading } from '../../commons';
|
|
12
13
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
13
14
|
|
|
14
15
|
navigator.geolocation = require('@react-native-community/geolocation');
|
|
@@ -34,6 +35,7 @@ const SelectAddress = memo(({ route }) => {
|
|
|
34
35
|
const [input, setInput] = useState('');
|
|
35
36
|
const [searchData, setSearchData] = useState([]);
|
|
36
37
|
const [searchedLocation, setSearchedLocation] = useState(null);
|
|
38
|
+
const [loading, setLoading] = useState(false);
|
|
37
39
|
const mapRef = useRef(null);
|
|
38
40
|
|
|
39
41
|
const onDone = useCallback(() => {
|
|
@@ -116,6 +118,7 @@ const SelectAddress = memo(({ route }) => {
|
|
|
116
118
|
async (position) => {
|
|
117
119
|
const currentLatitude = JSON.stringify(position.coords.latitude);
|
|
118
120
|
const currentLongitude = JSON.stringify(position.coords.longitude);
|
|
121
|
+
setLoading(true);
|
|
119
122
|
const { success, data } = await axiosGet(
|
|
120
123
|
API.EXTERNAL.GOOGLE_MAP.GET_LOCATION_FROM_LAT_LNG,
|
|
121
124
|
{
|
|
@@ -134,9 +137,11 @@ const SelectAddress = memo(({ route }) => {
|
|
|
134
137
|
longitude: result.geometry.location.lng,
|
|
135
138
|
});
|
|
136
139
|
}
|
|
140
|
+
setLoading(false);
|
|
137
141
|
},
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
|
143
|
+
(error) => {}
|
|
144
|
+
// { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 } enable on emulator
|
|
140
145
|
);
|
|
141
146
|
}, []);
|
|
142
147
|
|
|
@@ -233,6 +238,7 @@ const SelectAddress = memo(({ route }) => {
|
|
|
233
238
|
typeMain="primaryText"
|
|
234
239
|
typeSecondary="primaryText"
|
|
235
240
|
/>
|
|
241
|
+
{loading && <FullLoading />}
|
|
236
242
|
</View>
|
|
237
243
|
);
|
|
238
244
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import React, { memo, useCallback, useEffect, useState, useRef } from 'react';
|
|
2
2
|
import { AppState, ScrollView } from 'react-native';
|
|
3
3
|
import SummaryItem from '../../commons/SummaryItem';
|
|
4
4
|
import Routes from '../../utils/Route';
|
|
@@ -8,12 +8,11 @@ import { API } from '../../configs';
|
|
|
8
8
|
|
|
9
9
|
const Summaries = memo(({ unit }) => {
|
|
10
10
|
const [unitSummaries, setUnitSummaries] = useState([]);
|
|
11
|
-
const [appState, setAppState] = useState(AppState.currentState);
|
|
12
11
|
// eslint-disable-next-line no-unused-vars
|
|
13
12
|
const [localState, _] = useState({});
|
|
14
13
|
const isFocused = useIsFocused();
|
|
15
|
-
|
|
16
14
|
const navigation = useNavigation();
|
|
15
|
+
const appState = useRef(AppState.currentState);
|
|
17
16
|
|
|
18
17
|
const fetchUnitSummary = useCallback(async () => {
|
|
19
18
|
if (!unit.id) {
|
|
@@ -55,23 +54,21 @@ const Summaries = memo(({ unit }) => {
|
|
|
55
54
|
}
|
|
56
55
|
}, [localState, fetchUnitSummary]);
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
(nextAppState) => {
|
|
60
|
-
if (
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const subscription = AppState.addEventListener('change', (nextAppState) => {
|
|
59
|
+
if (
|
|
60
|
+
appState.current.match(/inactive|background/) &&
|
|
61
|
+
nextAppState === 'active'
|
|
62
|
+
) {
|
|
61
63
|
fetchUnitSummary();
|
|
62
64
|
}
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
[appState, fetchUnitSummary]
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
AppState.addEventListener('change', handleAppStateChange);
|
|
65
|
+
appState.current = nextAppState;
|
|
66
|
+
});
|
|
70
67
|
|
|
71
68
|
return () => {
|
|
72
|
-
|
|
69
|
+
subscription.remove();
|
|
73
70
|
};
|
|
74
|
-
}, [
|
|
71
|
+
}, [fetchUnitSummary]);
|
|
75
72
|
|
|
76
73
|
useEffect(() => {
|
|
77
74
|
if (!isFocused) {
|
|
@@ -514,4 +514,29 @@ describe('Test UnitDetail', () => {
|
|
|
514
514
|
const favorites = instance.findAllByType(SubUnitFavorites);
|
|
515
515
|
expect(favorites).toHaveLength(1);
|
|
516
516
|
});
|
|
517
|
+
test('render navbar', async () => {
|
|
518
|
+
const unitData = {
|
|
519
|
+
stations: [
|
|
520
|
+
{
|
|
521
|
+
isFavorites: true,
|
|
522
|
+
name: 'Favorites',
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
};
|
|
526
|
+
await act(async () => {
|
|
527
|
+
tree = await renderer.create(
|
|
528
|
+
wrapComponent({ params: { ...route.params, unitData } }, account)
|
|
529
|
+
);
|
|
530
|
+
});
|
|
531
|
+
const instance = tree.root;
|
|
532
|
+
const navBar = instance.find(
|
|
533
|
+
(el) => el.props.testID === TESTID.NAVBAR_ON_SNAP_ITEM
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
await act(() => {
|
|
537
|
+
navBar.props.onSnapToItem();
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
expect(navBar).toBeDefined();
|
|
541
|
+
});
|
|
517
542
|
});
|
|
@@ -8,6 +8,7 @@ import { mockSCStore } from '../../../../context/mockStore';
|
|
|
8
8
|
import Header from '../Header';
|
|
9
9
|
import ImageButton from '../../../../commons/ImageButton';
|
|
10
10
|
import Routes from '../../../../utils/Route';
|
|
11
|
+
import { TouchableOpacity } from 'react-native';
|
|
11
12
|
|
|
12
13
|
const wrapComponent = (title, goBack, dark, hideRight) => (
|
|
13
14
|
<SCProvider initState={mockSCStore({})}>
|
|
@@ -15,6 +16,19 @@ const wrapComponent = (title, goBack, dark, hideRight) => (
|
|
|
15
16
|
</SCProvider>
|
|
16
17
|
);
|
|
17
18
|
|
|
19
|
+
const mockedNavigate = jest.fn();
|
|
20
|
+
const mockedGoBack = jest.fn();
|
|
21
|
+
jest.mock('@react-navigation/native', () => {
|
|
22
|
+
return {
|
|
23
|
+
...jest.requireActual('@react-navigation/native'),
|
|
24
|
+
useNavigation: () => ({
|
|
25
|
+
navigate: mockedNavigate,
|
|
26
|
+
goBack: mockedGoBack,
|
|
27
|
+
}),
|
|
28
|
+
useIsFocused: () => true,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
18
32
|
describe('Test Header', () => {
|
|
19
33
|
let tree;
|
|
20
34
|
test('test hideModal', () => {
|
|
@@ -53,4 +67,22 @@ describe('Test Header', () => {
|
|
|
53
67
|
});
|
|
54
68
|
expect(popover.props.isVisible).toBe(false);
|
|
55
69
|
});
|
|
70
|
+
|
|
71
|
+
test('test onpress touchableOpacity', () => {
|
|
72
|
+
act(() => {
|
|
73
|
+
tree = renderer.create(wrapComponent());
|
|
74
|
+
});
|
|
75
|
+
const instance = tree.root;
|
|
76
|
+
const touchableOpacity = instance.findAllByType(TouchableOpacity);
|
|
77
|
+
|
|
78
|
+
act(() => {
|
|
79
|
+
touchableOpacity[0].props.onPress();
|
|
80
|
+
touchableOpacity[1].props.onPress();
|
|
81
|
+
touchableOpacity[2].props.onPress();
|
|
82
|
+
touchableOpacity[3].props.onPress();
|
|
83
|
+
});
|
|
84
|
+
expect(touchableOpacity).toHaveLength(4);
|
|
85
|
+
expect(touchableOpacity).toBeDefined();
|
|
86
|
+
expect(mockedGoBack).toHaveBeenCalled();
|
|
87
|
+
});
|
|
56
88
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useSCContextSelector } from '../../../context';
|
|
3
|
+
|
|
4
|
+
export const useFavorites = (stations, automatesData) => {
|
|
5
|
+
const favoriteDeviceIds = useSCContextSelector(
|
|
6
|
+
(state) => state.unit.favoriteDeviceIds
|
|
7
|
+
);
|
|
8
|
+
const starredScriptIds = useSCContextSelector(
|
|
9
|
+
(state) => state.automate.starredScriptIds
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const favoriteDevices = useMemo(() => {
|
|
13
|
+
return []
|
|
14
|
+
.concat(...stations.map((station) => station.sensors || []))
|
|
15
|
+
.filter((device) => favoriteDeviceIds.includes(device.id));
|
|
16
|
+
}, [stations, favoriteDeviceIds]);
|
|
17
|
+
|
|
18
|
+
const favoriteAutomates = useMemo(() => {
|
|
19
|
+
return []
|
|
20
|
+
.concat(...automatesData.map((automateData) => automateData.data || []))
|
|
21
|
+
.filter((automate) => starredScriptIds.includes(automate?.script?.id));
|
|
22
|
+
}, [automatesData, starredScriptIds]);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
favoriteDevices,
|
|
26
|
+
favoriteAutomates,
|
|
27
|
+
};
|
|
28
|
+
};
|
package/src/utils/Apis/axios.js
CHANGED
|
@@ -50,10 +50,11 @@ const parseErrorResponse = async (error) => {
|
|
|
50
50
|
error,
|
|
51
51
|
message,
|
|
52
52
|
data,
|
|
53
|
+
resp_status: error?.response?.status,
|
|
53
54
|
};
|
|
54
55
|
};
|
|
55
56
|
|
|
56
|
-
export async function axiosCache(URL) {
|
|
57
|
+
export async function axiosCache(URL, resp_status) {
|
|
57
58
|
const cacheKey = `@CACHE_REQUEST_${URL}`;
|
|
58
59
|
const cachedData = await getData(cacheKey);
|
|
59
60
|
if (!cachedData) {
|
|
@@ -64,6 +65,7 @@ export async function axiosCache(URL) {
|
|
|
64
65
|
success: true,
|
|
65
66
|
data: JSON.parse(cachedData),
|
|
66
67
|
cache: true,
|
|
68
|
+
resp_status,
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -89,7 +91,10 @@ export async function axiosGet(URL, config = {}, cache = false) {
|
|
|
89
91
|
if (cache) {
|
|
90
92
|
// only network error or server error
|
|
91
93
|
if (!error.response || error.response.status >= 500) {
|
|
92
|
-
return (
|
|
94
|
+
return (
|
|
95
|
+
(await axiosCache(URL, error.response.status)) ||
|
|
96
|
+
(await parseErrorResponse(error))
|
|
97
|
+
);
|
|
93
98
|
} else {
|
|
94
99
|
await deleteData(cacheKey);
|
|
95
100
|
}
|