@eohjsc/react-native-smart-city 0.3.26 → 0.3.27
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/Dashboard/MyUnit/__test__/MyUnit.test.js +55 -5
- package/src/commons/Dashboard/MyUnit/index.js +58 -11
- package/src/commons/MenuActionMore/index.js +1 -3
- package/src/commons/Popover/index.js +26 -0
- package/src/context/actionType.ts +2 -0
- package/src/context/reducer.ts +10 -0
- package/src/hooks/Common/useGGHomeDeviceConnected.js +9 -2
- package/src/hooks/Common/usePopover.js +0 -8
- package/src/hooks/IoT/useGGHomeConnection.js +0 -1
- package/src/navigations/UnitStack.js +10 -2
- package/src/screens/ScriptDetail/__test__/index.test.js +0 -3
- package/src/screens/ScriptDetail/index.js +7 -10
- package/src/screens/Unit/SmartAccount.js +7 -6
- package/src/screens/Unit/Summaries.js +8 -1
- package/src/screens/Unit/components/Header/index.js +1 -1
- package/src/screens/Unit/components/MyUnitDevice/index.js +29 -12
- package/src/screens/Unit/components/__test__/Header.test.js +1 -1
- package/src/screens/Unit/components/__test__/MyUnitDevice.test.js +2 -2
- package/src/screens/Unit/hook/useUnitConnectRemoteDevices.js +6 -5
- package/src/screens/Unit/components/MyUnit/index.js +0 -136
- package/src/screens/Unit/components/__test__/MyUnit.test.js +0 -35
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer, { act } from 'react-test-renderer';
|
|
3
|
+
import MockAdapter from 'axios-mock-adapter';
|
|
3
4
|
import MyUnit from '..';
|
|
5
|
+
import MyUnitDevice from '../../../../screens/Unit/components/MyUnitDevice';
|
|
4
6
|
import { TESTID } from '../../../../configs/Constants';
|
|
5
7
|
import { SCProvider } from '../../../../context';
|
|
6
8
|
import { mockSCStore } from '../../../../context/mockStore';
|
|
9
|
+
import api from '../../../../utils/Apis/axios';
|
|
10
|
+
import { API } from '../../../../configs';
|
|
11
|
+
|
|
12
|
+
const mock = new MockAdapter(api.axiosInstance);
|
|
7
13
|
|
|
8
14
|
const mockedNavigate = jest.fn();
|
|
9
|
-
const mockUseIsFocused = jest.fn();
|
|
10
15
|
const mockedDispatch = jest.fn();
|
|
11
16
|
|
|
12
17
|
jest.mock('@react-navigation/native', () => {
|
|
@@ -15,9 +20,8 @@ jest.mock('@react-navigation/native', () => {
|
|
|
15
20
|
useNavigation: () => ({
|
|
16
21
|
navigate: mockedNavigate,
|
|
17
22
|
}),
|
|
18
|
-
useIsFocused: () =>
|
|
19
|
-
|
|
20
|
-
}),
|
|
23
|
+
useIsFocused: () => true,
|
|
24
|
+
useFocusEffect: jest.fn(),
|
|
21
25
|
};
|
|
22
26
|
});
|
|
23
27
|
|
|
@@ -34,6 +38,42 @@ const wrapComponent = () => (
|
|
|
34
38
|
|
|
35
39
|
describe('Test MyUnit', () => {
|
|
36
40
|
let tree;
|
|
41
|
+
let data = [
|
|
42
|
+
{
|
|
43
|
+
id: 1,
|
|
44
|
+
name: 'name',
|
|
45
|
+
background: 'background',
|
|
46
|
+
abstract_devices: [
|
|
47
|
+
{
|
|
48
|
+
id: 1,
|
|
49
|
+
name: 'device',
|
|
50
|
+
is_managed_by_backend: true,
|
|
51
|
+
device_type: 'GOOGLE_HOME',
|
|
52
|
+
station_name: 'name',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 2,
|
|
56
|
+
name: 'device',
|
|
57
|
+
is_managed_by_backend: true,
|
|
58
|
+
device_type: '',
|
|
59
|
+
station_name: 'name',
|
|
60
|
+
quick_action: {
|
|
61
|
+
config_id: 1,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 2,
|
|
68
|
+
name: 'name2',
|
|
69
|
+
background: 'background',
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
mock.resetHistory();
|
|
75
|
+
});
|
|
76
|
+
|
|
37
77
|
const getElement = (instance) => {
|
|
38
78
|
const goToDetail = instance.findAll(
|
|
39
79
|
(item) => item.props.testID === TESTID.MY_UNIT_GO_TO_DETAIL
|
|
@@ -44,7 +84,7 @@ describe('Test MyUnit', () => {
|
|
|
44
84
|
return { goToDetail, textNoUnit };
|
|
45
85
|
};
|
|
46
86
|
|
|
47
|
-
test('
|
|
87
|
+
test('MyUnit no Unit', async () => {
|
|
48
88
|
await act(async () => {
|
|
49
89
|
tree = await renderer.create(wrapComponent());
|
|
50
90
|
});
|
|
@@ -52,4 +92,14 @@ describe('Test MyUnit', () => {
|
|
|
52
92
|
const { textNoUnit } = getElement(instance);
|
|
53
93
|
expect(textNoUnit[0]).toBeDefined();
|
|
54
94
|
});
|
|
95
|
+
|
|
96
|
+
test('MyUnit with unit', async () => {
|
|
97
|
+
mock.onGet(API.UNIT.MY_UNITS()).replyOnce(200, data);
|
|
98
|
+
await act(async () => {
|
|
99
|
+
tree = await renderer.create(wrapComponent());
|
|
100
|
+
});
|
|
101
|
+
const instance = tree.root;
|
|
102
|
+
const devices = instance.findAllByType(MyUnitDevice);
|
|
103
|
+
expect(devices).toHaveLength(2);
|
|
104
|
+
});
|
|
55
105
|
});
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
memo,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
} from 'react';
|
|
2
9
|
import { View, Image, TouchableOpacity, Dimensions } from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
useNavigation,
|
|
12
|
+
useIsFocused,
|
|
13
|
+
useFocusEffect,
|
|
14
|
+
} from '@react-navigation/native';
|
|
15
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
3
16
|
import { API, Colors, Images } from '../../../configs';
|
|
4
17
|
import Text from '../../Text';
|
|
5
18
|
import { fetchWithCache } from '../../../utils/Apis/axios';
|
|
@@ -7,10 +20,13 @@ import { fetchWithCache } from '../../../utils/Apis/axios';
|
|
|
7
20
|
import styles from './styles';
|
|
8
21
|
import { Section } from '../../Section';
|
|
9
22
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
10
|
-
import {
|
|
23
|
+
import { useUnitConnectRemoteDevices } from '../../../screens/Unit/hook/useUnitConnectRemoteDevices';
|
|
24
|
+
import { useWatchConfigs } from '../../../hooks/IoT';
|
|
25
|
+
import { SCContext } from '../../../context';
|
|
26
|
+
import { Action } from '../../../context/actionType';
|
|
11
27
|
|
|
12
28
|
import Carousel from 'react-native-snap-carousel';
|
|
13
|
-
import { TESTID } from '../../../configs/Constants';
|
|
29
|
+
import { TESTID, DEVICE_TYPE } from '../../../configs/Constants';
|
|
14
30
|
import Routes from '../../../utils/Route';
|
|
15
31
|
import MyUnitDevice from '../../../screens/Unit/components/MyUnitDevice';
|
|
16
32
|
|
|
@@ -20,21 +36,49 @@ const MyUnit = () => {
|
|
|
20
36
|
const isFocused = useIsFocused();
|
|
21
37
|
const navigation = useNavigation();
|
|
22
38
|
const [myUnits, setMyUnits] = useState([]);
|
|
39
|
+
const [slideIndex, setSlideIndex] = useState(0);
|
|
40
|
+
const { setAction } = useContext(SCContext);
|
|
23
41
|
|
|
24
42
|
const fetchMyUnitDashboard = useCallback(async () => {
|
|
25
43
|
await fetchWithCache(API.UNIT.MY_UNITS(), {}, (response) => {
|
|
26
44
|
const { success, data } = response;
|
|
27
|
-
|
|
28
|
-
setMyUnits(data);
|
|
29
|
-
}
|
|
45
|
+
success && setMyUnits(data);
|
|
30
46
|
});
|
|
31
47
|
}, [setMyUnits]);
|
|
32
48
|
|
|
33
49
|
useEffect(() => {
|
|
34
|
-
|
|
35
|
-
fetchMyUnitDashboard();
|
|
36
|
-
}
|
|
50
|
+
isFocused && fetchMyUnitDashboard();
|
|
37
51
|
}, [fetchMyUnitDashboard, isFocused]);
|
|
52
|
+
|
|
53
|
+
useFocusEffect(
|
|
54
|
+
useCallback(() => {
|
|
55
|
+
const unsubscribe = NetInfo.addEventListener((state) => {
|
|
56
|
+
setAction(Action.SET_NETWORK_CONNECTED, state.isConnected);
|
|
57
|
+
});
|
|
58
|
+
return () => unsubscribe();
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, [])
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
useUnitConnectRemoteDevices(myUnits[slideIndex]);
|
|
64
|
+
|
|
65
|
+
const configsNeedWatching = useMemo(() => {
|
|
66
|
+
const configIds = [];
|
|
67
|
+
myUnits.forEach((unit) => {
|
|
68
|
+
(unit?.abstract_devices || []).forEach((device) => {
|
|
69
|
+
if (
|
|
70
|
+
device?.quick_action?.config_id &&
|
|
71
|
+
device?.device_type !== DEVICE_TYPE.GOOGLE_HOME
|
|
72
|
+
) {
|
|
73
|
+
configIds.push(device.quick_action.config_id);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
return configIds;
|
|
78
|
+
}, [myUnits]);
|
|
79
|
+
|
|
80
|
+
useWatchConfigs(configsNeedWatching);
|
|
81
|
+
|
|
38
82
|
const goToDetail = useCallback(
|
|
39
83
|
(item) => {
|
|
40
84
|
navigation.navigate(Routes.UnitStack, {
|
|
@@ -47,6 +91,7 @@ const MyUnit = () => {
|
|
|
47
91
|
},
|
|
48
92
|
[navigation]
|
|
49
93
|
);
|
|
94
|
+
|
|
50
95
|
const _renderItem = useCallback(
|
|
51
96
|
({ item, index }) => {
|
|
52
97
|
const paddingLeft = index === 0 ? 0 : 8;
|
|
@@ -73,14 +118,15 @@ const MyUnit = () => {
|
|
|
73
118
|
/>
|
|
74
119
|
<Text style={styles.title}>{item.name}</Text>
|
|
75
120
|
</TouchableOpacity>
|
|
76
|
-
{item.
|
|
77
|
-
<MyUnitDevice key={
|
|
121
|
+
{(item?.abstract_devices || []).map((device, indexDevice) => (
|
|
122
|
+
<MyUnitDevice key={indexDevice} device={device} unit={item} />
|
|
78
123
|
))}
|
|
79
124
|
</View>
|
|
80
125
|
);
|
|
81
126
|
},
|
|
82
127
|
[myUnits.length, goToDetail]
|
|
83
128
|
);
|
|
129
|
+
|
|
84
130
|
return (
|
|
85
131
|
<>
|
|
86
132
|
<Section style={styles.boxTxtMyUnit}>
|
|
@@ -96,6 +142,7 @@ const MyUnit = () => {
|
|
|
96
142
|
itemWidth={screenWidth - 32}
|
|
97
143
|
renderItem={_renderItem}
|
|
98
144
|
inactiveSlideScale={1}
|
|
145
|
+
onSnapToItem={setSlideIndex}
|
|
99
146
|
/>
|
|
100
147
|
) : (
|
|
101
148
|
<View>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { TouchableOpacity, ScrollView } from 'react-native';
|
|
3
|
-
import Popover from '
|
|
3
|
+
import Popover from '../Popover';
|
|
4
4
|
|
|
5
5
|
import styles from './MenuActionMoreStyles';
|
|
6
6
|
import Text from '../Text';
|
|
@@ -12,7 +12,6 @@ const MenuActionMore = memo(
|
|
|
12
12
|
({
|
|
13
13
|
isVisible,
|
|
14
14
|
hideMore,
|
|
15
|
-
hideComplete,
|
|
16
15
|
listMenuItem,
|
|
17
16
|
childRef,
|
|
18
17
|
onItemClick,
|
|
@@ -46,7 +45,6 @@ const MenuActionMore = memo(
|
|
|
46
45
|
placement="bottom"
|
|
47
46
|
from={childRef}
|
|
48
47
|
onRequestClose={hideMore}
|
|
49
|
-
onCloseComplete={hideComplete}
|
|
50
48
|
isVisible={isVisible}
|
|
51
49
|
arrowStyle={styles.wrap}
|
|
52
50
|
>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React, { useContext } from 'react';
|
|
2
|
+
import Popover from 'react-native-popover-view';
|
|
3
|
+
import { SCContext } from '../../context';
|
|
4
|
+
import { Action } from '../../context/actionType';
|
|
5
|
+
|
|
6
|
+
const PopoverComponent = (props) => {
|
|
7
|
+
const { setAction } = useContext(SCContext);
|
|
8
|
+
|
|
9
|
+
const onCloseStart = () => {
|
|
10
|
+
setAction(Action.SET_POPOVER_ANIMATING, true);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const onCloseComplete = () => {
|
|
14
|
+
setAction(Action.SET_POPOVER_ANIMATING, false);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Popover
|
|
19
|
+
onCloseStart={onCloseStart}
|
|
20
|
+
onCloseComplete={onCloseComplete}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default PopoverComponent;
|
|
@@ -23,6 +23,7 @@ export const Action = {
|
|
|
23
23
|
NEED_UPDATE_VALUE_EVALUATIONS: 'NEED_UPDATE_VALUE_EVALUATIONS',
|
|
24
24
|
ON_RECEIVE_NOTIFICATION: 'ON_RECEIVE_NOTIFICATION',
|
|
25
25
|
SET_DEVICES_STATUS: 'SET_DEVICES_STATUS',
|
|
26
|
+
SET_POPOVER_ANIMATING: 'SET_POPOVER_ANIMATING',
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
export type AuthData = {
|
|
@@ -80,6 +81,7 @@ export type AppType = {
|
|
|
80
81
|
isNetworkConnected: boolean;
|
|
81
82
|
camera_opened: any[];
|
|
82
83
|
notificationData: any;
|
|
84
|
+
popoverAnimating: boolean;
|
|
83
85
|
};
|
|
84
86
|
|
|
85
87
|
export type IoTType = {
|
package/src/context/reducer.ts
CHANGED
|
@@ -60,6 +60,7 @@ export const initialState = {
|
|
|
60
60
|
isNetworkConnected: false,
|
|
61
61
|
camera_opened: [],
|
|
62
62
|
notificationData: null,
|
|
63
|
+
popoverAnimating: false,
|
|
63
64
|
},
|
|
64
65
|
iot: {
|
|
65
66
|
googlehome: {
|
|
@@ -342,6 +343,15 @@ export const reducer = (currentState: ContextData, action: Action) => {
|
|
|
342
343
|
},
|
|
343
344
|
};
|
|
344
345
|
|
|
346
|
+
case Action.SET_POPOVER_ANIMATING:
|
|
347
|
+
return {
|
|
348
|
+
...currentState,
|
|
349
|
+
app: {
|
|
350
|
+
...currentState.app,
|
|
351
|
+
popoverAnimating: payload,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
345
355
|
default:
|
|
346
356
|
return currentState;
|
|
347
357
|
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { useSCContextSelector } from '../../context';
|
|
2
2
|
|
|
3
3
|
const useGGHomeDeviceConnected = (device) => {
|
|
4
|
-
const
|
|
4
|
+
const connections = useSCContextSelector(
|
|
5
|
+
(state) => state.iot.googlehome.connections
|
|
6
|
+
);
|
|
7
|
+
const isNetworkConnected = useSCContextSelector(
|
|
8
|
+
(state) => state.app.isNetworkConnected
|
|
9
|
+
);
|
|
5
10
|
|
|
6
|
-
const isConnecting =
|
|
11
|
+
const isConnecting =
|
|
12
|
+
isNetworkConnected && !!device?.chip_id && !(device.chip_id in connections);
|
|
7
13
|
|
|
8
14
|
const isConnected =
|
|
15
|
+
isNetworkConnected &&
|
|
9
16
|
!!device?.chip_id &&
|
|
10
17
|
device.chip_id in connections &&
|
|
11
18
|
!!connections[device.chip_id];
|
|
@@ -2,14 +2,12 @@ import { useCallback, useState, useRef } from 'react';
|
|
|
2
2
|
|
|
3
3
|
const usePopover = () => {
|
|
4
4
|
const [showingPopover, setShowingPopover] = useState(false);
|
|
5
|
-
const [hidingPopoverComplete, setHidingPopoverComplete] = useState(true);
|
|
6
5
|
const childRef = useRef(null);
|
|
7
6
|
|
|
8
7
|
const showPopoverWithRef = useCallback(
|
|
9
8
|
(ref) => {
|
|
10
9
|
childRef.current = ref.current;
|
|
11
10
|
setShowingPopover(true);
|
|
12
|
-
setHidingPopoverComplete(false);
|
|
13
11
|
},
|
|
14
12
|
[childRef]
|
|
15
13
|
);
|
|
@@ -19,17 +17,11 @@ const usePopover = () => {
|
|
|
19
17
|
setShowingPopover(false);
|
|
20
18
|
}, [childRef]);
|
|
21
19
|
|
|
22
|
-
const hidePopoverComplete = useCallback(() => {
|
|
23
|
-
setHidingPopoverComplete(true);
|
|
24
|
-
}, []);
|
|
25
|
-
|
|
26
20
|
return {
|
|
27
21
|
childRef,
|
|
28
22
|
showingPopover,
|
|
29
23
|
showPopoverWithRef,
|
|
30
24
|
hidePopover,
|
|
31
|
-
hidingPopoverComplete,
|
|
32
|
-
hidePopoverComplete,
|
|
33
25
|
};
|
|
34
26
|
};
|
|
35
27
|
|
|
@@ -93,7 +93,11 @@ export const UnitStack = memo((props) => {
|
|
|
93
93
|
if (!id) {
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
const { success, data } = await axiosGet(
|
|
96
|
+
const { success, data } = await axiosGet(
|
|
97
|
+
API.UNIT.FAVOURITE_DEVICES(id),
|
|
98
|
+
{},
|
|
99
|
+
true
|
|
100
|
+
);
|
|
97
101
|
success && setAction(Action.SET_FAVORITE_DEVICES, data);
|
|
98
102
|
};
|
|
99
103
|
fetchFavoriteDevices();
|
|
@@ -102,7 +106,11 @@ export const UnitStack = memo((props) => {
|
|
|
102
106
|
|
|
103
107
|
useEffect(() => {
|
|
104
108
|
const fetchStarredScripts = async () => {
|
|
105
|
-
const { success, data } = await axiosGet(
|
|
109
|
+
const { success, data } = await axiosGet(
|
|
110
|
+
API.AUTOMATE.STARRED_SCRIPTS(),
|
|
111
|
+
{},
|
|
112
|
+
true
|
|
113
|
+
);
|
|
106
114
|
success && setAction(Action.SET_STARRED_SCRIPTS, data);
|
|
107
115
|
};
|
|
108
116
|
fetchStarredScripts();
|
|
@@ -103,7 +103,6 @@ describe('Test ScriptDetail', () => {
|
|
|
103
103
|
|
|
104
104
|
await act(async () => {
|
|
105
105
|
await menu.props.onItemClick(rename);
|
|
106
|
-
await menu.props.hideComplete();
|
|
107
106
|
});
|
|
108
107
|
expect(menu.props.isVisible).toBeFalsy();
|
|
109
108
|
expect(alertAction.props.visible).toBeTruthy();
|
|
@@ -130,7 +129,6 @@ describe('Test ScriptDetail', () => {
|
|
|
130
129
|
|
|
131
130
|
await act(async () => {
|
|
132
131
|
await menu.props.onItemClick(rename);
|
|
133
|
-
await menu.props.hideComplete();
|
|
134
132
|
});
|
|
135
133
|
expect(menu.props.isVisible).toBeFalsy();
|
|
136
134
|
expect(alertAction.props.visible).toBeTruthy();
|
|
@@ -154,7 +152,6 @@ describe('Test ScriptDetail', () => {
|
|
|
154
152
|
|
|
155
153
|
await act(async () => {
|
|
156
154
|
await menu.props.onItemClick(deleteItem);
|
|
157
|
-
await menu.props.hideComplete();
|
|
158
155
|
});
|
|
159
156
|
expect(alertAction.props.visible).toBeTruthy();
|
|
160
157
|
mock.onDelete(API.AUTOMATE.SCRIPT(1)).reply(204);
|
|
@@ -43,6 +43,7 @@ import { popAction } from '../../navigations/utils';
|
|
|
43
43
|
import { TESTID } from '../../configs/Constants';
|
|
44
44
|
import useKeyboardAnimated from '../../hooks/Explore/useKeyboardAnimated';
|
|
45
45
|
import { REPEAT_OPTIONS } from '../SetSchedule/components/RepeatOptionsPopup';
|
|
46
|
+
import { useSCContextSelector } from '../../context';
|
|
46
47
|
|
|
47
48
|
const PreventDoubleTouch = withPreventDoubleClick(TouchableOpacity);
|
|
48
49
|
|
|
@@ -50,14 +51,8 @@ const ScriptDetail = ({ route }) => {
|
|
|
50
51
|
const { navigate, goBack, dispatch } = useNavigation();
|
|
51
52
|
const { params = {} } = route;
|
|
52
53
|
const refMenuAction = useRef();
|
|
53
|
-
const {
|
|
54
|
-
|
|
55
|
-
showingPopover,
|
|
56
|
-
showPopoverWithRef,
|
|
57
|
-
hidePopover,
|
|
58
|
-
hidingPopoverComplete,
|
|
59
|
-
hidePopoverComplete,
|
|
60
|
-
} = usePopover();
|
|
54
|
+
const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
|
|
55
|
+
usePopover();
|
|
61
56
|
const t = useTranslations();
|
|
62
57
|
const {
|
|
63
58
|
id,
|
|
@@ -84,6 +79,9 @@ const ScriptDetail = ({ route }) => {
|
|
|
84
79
|
const [data, setData] = useState([]);
|
|
85
80
|
|
|
86
81
|
const { isStarred, starScript, unstarScript } = useStarredScript(automate);
|
|
82
|
+
const popoverAnimating = useSCContextSelector(
|
|
83
|
+
(state) => state.app.popoverAnimating
|
|
84
|
+
);
|
|
87
85
|
|
|
88
86
|
const [transY] = useKeyboardAnimated(-16);
|
|
89
87
|
const animatedStyle = Platform.select({
|
|
@@ -483,7 +481,6 @@ const ScriptDetail = ({ route }) => {
|
|
|
483
481
|
<MenuActionMore
|
|
484
482
|
isVisible={showingPopover}
|
|
485
483
|
hideMore={hidePopover}
|
|
486
|
-
hideComplete={hidePopoverComplete}
|
|
487
484
|
listMenuItem={listMenuItem}
|
|
488
485
|
childRef={childRef}
|
|
489
486
|
onItemClick={onItemClick}
|
|
@@ -491,7 +488,7 @@ const ScriptDetail = ({ route }) => {
|
|
|
491
488
|
wrapStyle={styles.wrapStyle}
|
|
492
489
|
/>
|
|
493
490
|
<AlertAction
|
|
494
|
-
visible={stateAlertAction.visible &&
|
|
491
|
+
visible={stateAlertAction.visible && !popoverAnimating}
|
|
495
492
|
hideModal={hideAlertAction}
|
|
496
493
|
title={stateAlertAction.title}
|
|
497
494
|
message={stateAlertAction.message}
|
|
@@ -14,11 +14,12 @@ import Routes from '../../utils/Route';
|
|
|
14
14
|
import { useNavigation } from '@react-navigation/native';
|
|
15
15
|
import { axiosDelete, axiosGet } from '../../utils/Apis/axios';
|
|
16
16
|
import { SmartAccountItem } from './SmartAccountItem';
|
|
17
|
-
import { usePopover
|
|
17
|
+
import { usePopover } from '../../hooks/Common';
|
|
18
18
|
import { MenuActionMore, AlertAction, FullLoading } from '../../commons';
|
|
19
19
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
20
20
|
import { useStateAlertRemove } from '../Unit/hook/useStateAlertRemove';
|
|
21
21
|
import { ToastBottomHelper } from '../../utils/Utils';
|
|
22
|
+
import { useSCContextSelector } from '../../context';
|
|
22
23
|
|
|
23
24
|
const ListSmartAccount = ({ route }) => {
|
|
24
25
|
const { unitId } = route?.params || {};
|
|
@@ -27,6 +28,9 @@ const ListSmartAccount = ({ route }) => {
|
|
|
27
28
|
const smartAccountRef = useRef(null);
|
|
28
29
|
const { navigate } = useNavigation();
|
|
29
30
|
const [loadingRemoveItem, setLoadingRemoveItem] = useState(false);
|
|
31
|
+
const popoverAnimating = useSCContextSelector(
|
|
32
|
+
(state) => state.app.popoverAnimating
|
|
33
|
+
);
|
|
30
34
|
|
|
31
35
|
const getAllSmartAccounts = useCallback(async () => {
|
|
32
36
|
const { success, data: accountData } = await axiosGet(
|
|
@@ -39,7 +43,6 @@ const ListSmartAccount = ({ route }) => {
|
|
|
39
43
|
|
|
40
44
|
const { childRef, showingPopover, showPopoverWithRef, hidePopover } =
|
|
41
45
|
usePopover();
|
|
42
|
-
const [lockShowing, acquireLockShowing, releaseLockShowing] = useBoolean();
|
|
43
46
|
const { stateAlertRemove, onShowRemoveAlert, hideAlertAction } =
|
|
44
47
|
useStateAlertRemove();
|
|
45
48
|
|
|
@@ -62,11 +65,10 @@ const ListSmartAccount = ({ route }) => {
|
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
if (item.action === 'remove') {
|
|
65
|
-
acquireLockShowing();
|
|
66
68
|
onShowRemoveAlert(smartAccountRef.current.brand)();
|
|
67
69
|
}
|
|
68
70
|
},
|
|
69
|
-
[
|
|
71
|
+
[onShowRemoveAlert]
|
|
70
72
|
);
|
|
71
73
|
|
|
72
74
|
const deleteSmartAccount = useCallback(async () => {
|
|
@@ -124,7 +126,7 @@ const ListSmartAccount = ({ route }) => {
|
|
|
124
126
|
})}
|
|
125
127
|
</View>
|
|
126
128
|
<AlertAction
|
|
127
|
-
visible={stateAlertRemove.visible && !
|
|
129
|
+
visible={stateAlertRemove.visible && !popoverAnimating}
|
|
128
130
|
hideModal={hideAlertAction}
|
|
129
131
|
title={stateAlertRemove.title}
|
|
130
132
|
message={stateAlertRemove.message}
|
|
@@ -142,7 +144,6 @@ const ListSmartAccount = ({ route }) => {
|
|
|
142
144
|
listMenuItem={listMenuItem}
|
|
143
145
|
childRef={childRef}
|
|
144
146
|
onItemClick={onItemClick}
|
|
145
|
-
hideComplete={releaseLockShowing}
|
|
146
147
|
/>
|
|
147
148
|
</WrapHeaderScrollable>
|
|
148
149
|
{loadingRemoveItem && <FullLoading />}
|
|
@@ -6,6 +6,7 @@ import { useIsFocused, useNavigation } from '@react-navigation/native';
|
|
|
6
6
|
import { axiosGet } from '../../utils/Apis/axios';
|
|
7
7
|
import { API } from '../../configs';
|
|
8
8
|
import { useReceiveNotifications } from '../../hooks';
|
|
9
|
+
import { useSCContextSelector } from '../../context';
|
|
9
10
|
|
|
10
11
|
const Summaries = memo(({ unit }) => {
|
|
11
12
|
const [unitSummaries, setUnitSummaries] = useState([]);
|
|
@@ -14,6 +15,9 @@ const Summaries = memo(({ unit }) => {
|
|
|
14
15
|
const isFocused = useIsFocused();
|
|
15
16
|
const navigation = useNavigation();
|
|
16
17
|
const appState = useRef(AppState.currentState);
|
|
18
|
+
const popoverAnimating = useSCContextSelector(
|
|
19
|
+
(state) => state.app.popoverAnimating
|
|
20
|
+
);
|
|
17
21
|
|
|
18
22
|
const fetchUnitSummary = useCallback(async () => {
|
|
19
23
|
if (!unit.id) {
|
|
@@ -33,6 +37,9 @@ const Summaries = memo(({ unit }) => {
|
|
|
33
37
|
|
|
34
38
|
const goToSummary = useCallback(
|
|
35
39
|
(summary) => {
|
|
40
|
+
if (popoverAnimating) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
36
43
|
navigation.navigate(Routes.UnitSummary, {
|
|
37
44
|
summaryId: summary.id,
|
|
38
45
|
unitId: unit.id,
|
|
@@ -40,7 +47,7 @@ const Summaries = memo(({ unit }) => {
|
|
|
40
47
|
unitData: unit,
|
|
41
48
|
});
|
|
42
49
|
},
|
|
43
|
-
[navigation, unit]
|
|
50
|
+
[navigation, popoverAnimating, unit]
|
|
44
51
|
);
|
|
45
52
|
|
|
46
53
|
const continuousFetchSummary = useCallback(async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useRef, useState } from 'react';
|
|
2
2
|
import { Dimensions, View, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
-
import Popover from '
|
|
3
|
+
import Popover from '../../../../commons/Popover';
|
|
4
4
|
import { IconOutline } from '@ant-design/icons-react-native';
|
|
5
5
|
import { useTranslations } from '../../../../hooks/Common/useTranslations';
|
|
6
6
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import React, { useState, useCallback } from 'react';
|
|
2
2
|
import { StyleSheet, View, TouchableOpacity } from 'react-native';
|
|
3
3
|
import { useNavigation } from '@react-navigation/native';
|
|
4
|
+
import { useGGHomeDeviceConnected } from '../../../../hooks/Common';
|
|
4
5
|
|
|
5
6
|
import ItemQuickAction from '../../../../commons/Action/ItemQuickAction';
|
|
6
7
|
import Text from '../../../../commons/Text';
|
|
7
8
|
import Routes from '../../../../utils/Route';
|
|
8
9
|
import { Colors } from '../../../../configs';
|
|
9
10
|
import FImage from '../../../../commons/FImage';
|
|
11
|
+
import { DEVICE_TYPE } from '../../../../configs/Constants';
|
|
10
12
|
|
|
11
|
-
const MyUnitDevice = ({
|
|
12
|
-
const [status, setStatus] = useState(
|
|
13
|
+
const MyUnitDevice = ({ device, unit }) => {
|
|
14
|
+
const [status, setStatus] = useState(device.status);
|
|
13
15
|
const { navigate } = useNavigation();
|
|
14
16
|
|
|
15
17
|
const goToSensorDisplay = useCallback(() => {
|
|
@@ -17,34 +19,49 @@ const MyUnitDevice = ({ sensor, unit }) => {
|
|
|
17
19
|
screen: Routes.DeviceDetail,
|
|
18
20
|
params: {
|
|
19
21
|
unitData: unit,
|
|
20
|
-
sensorData:
|
|
22
|
+
sensorData: device,
|
|
21
23
|
},
|
|
22
24
|
});
|
|
23
|
-
}, [navigate,
|
|
25
|
+
}, [navigate, device, unit]);
|
|
26
|
+
|
|
27
|
+
const { isConnecting: isGGHomeConnecting } = useGGHomeDeviceConnected(device);
|
|
28
|
+
|
|
29
|
+
const canRenderQuickAction = (() => {
|
|
30
|
+
if (
|
|
31
|
+
!!device &&
|
|
32
|
+
!device?.is_managed_by_backend &&
|
|
33
|
+
device?.device_type === DEVICE_TYPE.GOOGLE_HOME
|
|
34
|
+
) {
|
|
35
|
+
return !isGGHomeConnecting;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
})();
|
|
24
39
|
|
|
25
40
|
return (
|
|
26
41
|
<View style={styles.item}>
|
|
27
42
|
<TouchableOpacity style={styles.flex1} onPress={goToSensorDisplay}>
|
|
28
43
|
<View style={styles.rowCenter}>
|
|
29
|
-
<FImage style={styles.image} source={{ uri:
|
|
44
|
+
<FImage style={styles.image} source={{ uri: device?.icon_kit }} />
|
|
30
45
|
<View style={styles.marginTop3}>
|
|
31
46
|
<Text numberOfLines={1} semibold style={styles.nameDevice}>
|
|
32
|
-
{
|
|
47
|
+
{device.name}
|
|
33
48
|
</Text>
|
|
34
49
|
<View style={styles.roomDevice}>
|
|
35
50
|
<Text numberOfLines={1} style={styles.roomDevicePart}>
|
|
36
|
-
{
|
|
51
|
+
{device.station_name}
|
|
37
52
|
{status ? ` - ${status}` : ''}
|
|
38
53
|
</Text>
|
|
39
54
|
</View>
|
|
40
55
|
</View>
|
|
41
56
|
</View>
|
|
42
57
|
</TouchableOpacity>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
{canRenderQuickAction && (
|
|
59
|
+
<ItemQuickAction
|
|
60
|
+
sensor={device}
|
|
61
|
+
wrapperStyle={styles.iconCircle}
|
|
62
|
+
setStatus={setStatus}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
48
65
|
</View>
|
|
49
66
|
);
|
|
50
67
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer, { act } from 'react-test-renderer';
|
|
3
3
|
import Modal from 'react-native-modal';
|
|
4
|
-
import Popover from '
|
|
4
|
+
import Popover from '../../../../commons/Popover';
|
|
5
5
|
|
|
6
6
|
import { SCProvider } from '../../../../context';
|
|
7
7
|
import { mockSCStore } from '../../../../context/mockStore';
|
|
@@ -26,7 +26,7 @@ describe('Test MyUnitDevice', () => {
|
|
|
26
26
|
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
props = {
|
|
29
|
-
|
|
29
|
+
device: {
|
|
30
30
|
status: 'Ok',
|
|
31
31
|
name: 'Test',
|
|
32
32
|
station_name: '',
|
|
@@ -54,7 +54,7 @@ describe('Test MyUnitDevice', () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it('Test render without status', async () => {
|
|
57
|
-
props.
|
|
57
|
+
props.device.status = undefined;
|
|
58
58
|
await act(() => {
|
|
59
59
|
tree = create(wrapComponent(props));
|
|
60
60
|
});
|
|
@@ -5,7 +5,9 @@ import { useGGHomeConnection } from '../../../hooks/IoT';
|
|
|
5
5
|
import { useSCContextSelector } from '../../../context';
|
|
6
6
|
|
|
7
7
|
export const useUnitConnectRemoteDevices = (unit) => {
|
|
8
|
-
const
|
|
8
|
+
const isNetworkConnected = useSCContextSelector(
|
|
9
|
+
(state) => state.app.isNetworkConnected
|
|
10
|
+
);
|
|
9
11
|
|
|
10
12
|
const { connectGoogleHome } = useGGHomeConnection();
|
|
11
13
|
|
|
@@ -21,15 +23,14 @@ export const useUnitConnectRemoteDevices = (unit) => {
|
|
|
21
23
|
}, []);
|
|
22
24
|
|
|
23
25
|
useEffect(() => {
|
|
24
|
-
if (unit
|
|
26
|
+
if (unit?.remote_control_options?.bluetooth) {
|
|
25
27
|
scanBluetoothDevices(unit.remote_control_options.bluetooth);
|
|
26
28
|
}
|
|
27
29
|
}, [unit]);
|
|
28
30
|
|
|
29
31
|
useEffect(() => {
|
|
30
32
|
if (
|
|
31
|
-
unit
|
|
32
|
-
unit.remote_control_options.googlehome?.length &&
|
|
33
|
+
unit?.remote_control_options?.googlehome?.length &&
|
|
33
34
|
isNetworkConnected
|
|
34
35
|
) {
|
|
35
36
|
(async () => {
|
|
@@ -40,7 +41,7 @@ export const useUnitConnectRemoteDevices = (unit) => {
|
|
|
40
41
|
}, [unit, isNetworkConnected]);
|
|
41
42
|
|
|
42
43
|
useEffect(() => {
|
|
43
|
-
if (unit
|
|
44
|
+
if (unit?.remote_control_options?.lg_thinq) {
|
|
44
45
|
(async () => {
|
|
45
46
|
await handleLgThinqConnect(unit.remote_control_options.lg_thinq);
|
|
46
47
|
})();
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import React, { useMemo, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
View,
|
|
4
|
-
Image,
|
|
5
|
-
TouchableOpacity,
|
|
6
|
-
StyleSheet,
|
|
7
|
-
Dimensions,
|
|
8
|
-
} from 'react-native';
|
|
9
|
-
import Carousel from 'react-native-snap-carousel';
|
|
10
|
-
import { useNavigation } from '@react-navigation/native';
|
|
11
|
-
import { useTranslations } from '../../../../hooks/Common/useTranslations';
|
|
12
|
-
|
|
13
|
-
import { Colors, Images } from '../../../../configs';
|
|
14
|
-
import { TESTID } from '../../../../configs/Constants';
|
|
15
|
-
import Text from '../../../../commons/Text';
|
|
16
|
-
import MyUnitDevice from '../MyUnitDevice';
|
|
17
|
-
import Routes from '../../../../utils/Route';
|
|
18
|
-
import { colorOpacity } from '../../../../utils/Converter/color';
|
|
19
|
-
|
|
20
|
-
let screenWidth = Dimensions.get('window').width;
|
|
21
|
-
|
|
22
|
-
const MyUnit = ({ myUnits }) => {
|
|
23
|
-
const t = useTranslations();
|
|
24
|
-
const navigation = useNavigation();
|
|
25
|
-
const carouselItems = useMemo(() => myUnits, [myUnits]);
|
|
26
|
-
const goToDetail = useCallback(
|
|
27
|
-
(item) => {
|
|
28
|
-
navigation.navigate(Routes.UnitStack, {
|
|
29
|
-
screen: Routes.UnitDetail,
|
|
30
|
-
params: {
|
|
31
|
-
unitId: item.id,
|
|
32
|
-
unitData: item,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
},
|
|
36
|
-
[navigation]
|
|
37
|
-
);
|
|
38
|
-
const _renderItem = useCallback(
|
|
39
|
-
({ item, index }) => {
|
|
40
|
-
const paddingLeft = index === 0 ? 0 : 8;
|
|
41
|
-
const paddingRight = index === carouselItems.length - 1 ? 0 : 8;
|
|
42
|
-
return (
|
|
43
|
-
<View
|
|
44
|
-
style={{
|
|
45
|
-
paddingLeft: paddingLeft,
|
|
46
|
-
paddingRight: paddingRight,
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
<TouchableOpacity
|
|
50
|
-
onPress={() => goToDetail(item)}
|
|
51
|
-
style={styles.btnItem}
|
|
52
|
-
activeOpacity={0.75}
|
|
53
|
-
testID={TESTID.MY_UNIT_GO_TO_DETAIL}
|
|
54
|
-
>
|
|
55
|
-
<View style={styles.overlay} />
|
|
56
|
-
<Image
|
|
57
|
-
style={styles.bgMyUnit}
|
|
58
|
-
source={{ uri: item.background }}
|
|
59
|
-
defaultSource={Images.BgUnit}
|
|
60
|
-
resizeMode="cover"
|
|
61
|
-
/>
|
|
62
|
-
<Text style={styles.title}>{item.name}</Text>
|
|
63
|
-
</TouchableOpacity>
|
|
64
|
-
{item.abstract_sensors.map((sensor, indexSensor) => (
|
|
65
|
-
<MyUnitDevice key={indexSensor} sensor={sensor} />
|
|
66
|
-
))}
|
|
67
|
-
</View>
|
|
68
|
-
);
|
|
69
|
-
},
|
|
70
|
-
[carouselItems.length, goToDetail]
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<View style={styles.container}>
|
|
75
|
-
{carouselItems.length ? (
|
|
76
|
-
<Carousel
|
|
77
|
-
layout={'default'}
|
|
78
|
-
data={carouselItems}
|
|
79
|
-
sliderWidth={screenWidth}
|
|
80
|
-
itemWidth={screenWidth - 32}
|
|
81
|
-
renderItem={_renderItem}
|
|
82
|
-
inactiveSlideScale={1}
|
|
83
|
-
/>
|
|
84
|
-
) : (
|
|
85
|
-
<View>
|
|
86
|
-
<Text testID={TESTID.MY_UNIT_NO_UNIT}>{t('text_no_units')}</Text>
|
|
87
|
-
</View>
|
|
88
|
-
)}
|
|
89
|
-
</View>
|
|
90
|
-
);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const styles = StyleSheet.create({
|
|
94
|
-
container: {
|
|
95
|
-
flex: 1,
|
|
96
|
-
backgroundColor: Colors.White,
|
|
97
|
-
flexDirection: 'row',
|
|
98
|
-
justifyContent: 'center',
|
|
99
|
-
paddingBottom: 16,
|
|
100
|
-
},
|
|
101
|
-
overlay: {
|
|
102
|
-
backgroundColor: colorOpacity(Colors.Black, 0.4),
|
|
103
|
-
zIndex: 2,
|
|
104
|
-
left: 0,
|
|
105
|
-
top: 0,
|
|
106
|
-
position: 'absolute',
|
|
107
|
-
width: '100%',
|
|
108
|
-
height: '100%',
|
|
109
|
-
borderRadius: 10,
|
|
110
|
-
},
|
|
111
|
-
bgMyUnit: {
|
|
112
|
-
position: 'absolute',
|
|
113
|
-
left: 0,
|
|
114
|
-
top: 0,
|
|
115
|
-
right: 0,
|
|
116
|
-
width: '100%',
|
|
117
|
-
height: '100%',
|
|
118
|
-
borderRadius: 10,
|
|
119
|
-
},
|
|
120
|
-
title: {
|
|
121
|
-
position: 'absolute',
|
|
122
|
-
zIndex: 3,
|
|
123
|
-
fontWeight: 'bold',
|
|
124
|
-
fontSize: 20,
|
|
125
|
-
lineHeight: 28,
|
|
126
|
-
color: Colors.White,
|
|
127
|
-
bottom: 16,
|
|
128
|
-
left: 16,
|
|
129
|
-
},
|
|
130
|
-
btnItem: {
|
|
131
|
-
height: 121,
|
|
132
|
-
marginBottom: 16,
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
export default MyUnit;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import Carousel from 'react-native-snap-carousel';
|
|
3
|
-
import { act, create } from 'react-test-renderer';
|
|
4
|
-
import MyUnit from '../MyUnit';
|
|
5
|
-
import { SCProvider } from '../../../../context';
|
|
6
|
-
import { mockSCStore } from '../../../../context/mockStore';
|
|
7
|
-
|
|
8
|
-
const wrapComponent = (units) => (
|
|
9
|
-
<SCProvider initState={mockSCStore({})}>
|
|
10
|
-
<MyUnit myUnits={units} />
|
|
11
|
-
</SCProvider>
|
|
12
|
-
);
|
|
13
|
-
jest.mock('react', () => {
|
|
14
|
-
return {
|
|
15
|
-
...jest.requireActual('react'),
|
|
16
|
-
memo: (x) => x,
|
|
17
|
-
};
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('Test MyUnit', () => {
|
|
21
|
-
let tree;
|
|
22
|
-
|
|
23
|
-
it('render MyUnit carousel', async () => {
|
|
24
|
-
const units = [
|
|
25
|
-
{ id: 1, name: '', abstract_sensors: [{ id: 1, name: '' }] },
|
|
26
|
-
{ id: 2, name: '', abstract_sensors: [{ id: 1, name: '' }] },
|
|
27
|
-
];
|
|
28
|
-
await act(() => {
|
|
29
|
-
tree = create(wrapComponent(units));
|
|
30
|
-
});
|
|
31
|
-
const instance = tree.root;
|
|
32
|
-
const carousel = instance.findAllByType(Carousel);
|
|
33
|
-
expect(carousel).toHaveLength(1);
|
|
34
|
-
});
|
|
35
|
-
});
|