@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.3.26",
4
+ "version": "0.3.27",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -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
- isFocused: mockUseIsFocused,
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('create MyUnit no Unit', async () => {
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, { memo, useCallback, useEffect, useState } from '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 { useNavigation, useIsFocused } from '@react-navigation/native';
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
- if (success) {
28
- setMyUnits(data);
29
- }
45
+ success && setMyUnits(data);
30
46
  });
31
47
  }, [setMyUnits]);
32
48
 
33
49
  useEffect(() => {
34
- if (isFocused) {
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.abstract_sensors.map((sensor, indexSensor) => (
77
- <MyUnitDevice key={indexSensor} sensor={sensor} unit={item} />
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 'react-native-popover-view';
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 = {
@@ -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 { connections } = useSCContextSelector((state) => state.iot.googlehome);
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 = !!device?.chip_id && !(device.chip_id in connections);
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
 
@@ -71,7 +71,6 @@ const useGGHomeConnection = () => {
71
71
  onReconnected
72
72
  );
73
73
  },
74
- // eslint-disable-next-line react-hooks/exhaustive-deps
75
74
  [connections, onEstablished, onDisconnected, onReconnected]
76
75
  );
77
76
 
@@ -93,7 +93,11 @@ export const UnitStack = memo((props) => {
93
93
  if (!id) {
94
94
  return;
95
95
  }
96
- const { success, data } = await axiosGet(API.UNIT.FAVOURITE_DEVICES(id));
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(API.AUTOMATE.STARRED_SCRIPTS());
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
- childRef,
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 && hidingPopoverComplete}
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, useBoolean } from '../../hooks/Common';
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
- [acquireLockShowing, onShowRemoveAlert]
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 && !lockShowing}
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 'react-native-popover-view';
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 = ({ sensor, unit }) => {
12
- const [status, setStatus] = useState(sensor.status);
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: sensor,
22
+ sensorData: device,
21
23
  },
22
24
  });
23
- }, [navigate, sensor, unit]);
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: sensor?.icon_kit }} />
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
- {sensor.name}
47
+ {device.name}
33
48
  </Text>
34
49
  <View style={styles.roomDevice}>
35
50
  <Text numberOfLines={1} style={styles.roomDevicePart}>
36
- {sensor.station_name}
51
+ {device.station_name}
37
52
  {status ? ` - ${status}` : ''}
38
53
  </Text>
39
54
  </View>
40
55
  </View>
41
56
  </View>
42
57
  </TouchableOpacity>
43
- <ItemQuickAction
44
- sensor={sensor}
45
- wrapperStyle={styles.iconCircle}
46
- setStatus={setStatus}
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 'react-native-popover-view';
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
- sensor: {
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.sensor.status = undefined;
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 { isNetworkConnected } = useSCContextSelector((state) => state.app);
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.remote_control_options && unit.remote_control_options.bluetooth) {
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.remote_control_options &&
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.remote_control_options && unit.remote_control_options.lg_thinq) {
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
- });